自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一篇學(xué)會 InnoDB 表空間

網(wǎng)絡(luò) 通信技術(shù)
我們存在 MySQL 中的數(shù)據(jù),到底在磁盤上長啥樣。你可能會說,數(shù)據(jù)不都存儲在聚簇索引中嗎?但很遺憾,你并沒有回答我的問題。我會再問你,那聚簇索引在磁盤上又長啥樣?

[[408831]]

本文轉(zhuǎn)載自微信公眾號「SH的全棧筆記」,作者SH的全棧筆記。轉(zhuǎn)載本文請聯(lián)系SH的全棧筆記公眾號。

想了很久的標(biāo)題,算了,就這樣吧。

這應(yīng)該是 MySQL 原理中最底層的部分了,我們存在 MySQL 中的數(shù)據(jù),到底在磁盤上長啥樣。你可能會說,數(shù)據(jù)不都存儲在聚簇索引中嗎?但很遺憾,你并沒有回答我的問題。我會再問你,那聚簇索引在磁盤上又長啥樣?

就像 Redis 的 RDB 文件,最終落在磁盤上就是一個(gè)真真切切的 dump.rdb 文件,而 MySQL 就顯得有點(diǎn)迷,我們只知道通過 SQL 去拿數(shù)據(jù),并不知道數(shù)據(jù)最終是以什么方式進(jìn)行存儲的。當(dāng)然,了解其底層的存儲邏輯,并不僅僅是為了滿足好奇心這么簡單。

其底層的存儲方式,會影響到聚簇索引中數(shù)據(jù)的存儲,進(jìn)而影響到 MySQL 的 DML(Data Manipulation Language) 性能,所以對底層存儲邏輯有個(gè)清晰的認(rèn)知,就能夠在某些對性能有著極致追求的場景下,幫助我們對 MySQL 進(jìn)行優(yōu)化。

表在磁盤上到底長啥樣

首先我們先不扯像表空間這類的專業(yè)詞匯,讓我們先來建一張表,從磁盤的結(jié)構(gòu)上來看一下。首先你得找到 MySQL 的數(shù)據(jù)目錄,如果你是用 Docker 啟動的話,這個(gè)目錄大概長下面這樣:

  1. /data00/docker/volumes/ef876f70d5f5c95325c2a79689db79cc4d1cecb7d96e98901256bd49ca359287/_data 

然后我們新建一個(gè)叫 test 的 DB,然后在 _data 的這個(gè)目錄下就會多一個(gè) test 的目錄。然后在 test 數(shù)據(jù)庫下新建了一張 student 的表,在 test 目錄下就會多出兩個(gè)文件,分別是 student.frm 和 student.ibd。

可以發(fā)現(xiàn),最終數(shù)據(jù)在磁盤上的宏觀表現(xiàn)其實(shí)很簡單,就這么些個(gè)文件,什么索引啊、頁啊都先忽略不管。

對于后綴為 .frm 文件,里面都有啥?里面包含了每張表的元數(shù)據(jù),包括了表的結(jié)構(gòu)定義。而 .ibd 文件里則存放了該表的數(shù)據(jù)和索引。

我看到有人在博客里把 .ibd 寫成了 .idb...雖然 db 看著更順,但很遺憾并不正確,你把 ibd 的全稱 innodb data 記住,就不會把縮寫記錯(cuò)了。

上面說的這個(gè)以表名命名的 ibd 文件,其實(shí)還有一個(gè)專業(yè)術(shù)語叫表空間。

顧名思義可以理解為我這個(gè)表專屬的空間。

認(rèn)識表空間

如果我上來就直接告訴你,InnoDB 中有個(gè)概念叫表空間,你大概率是很難理解的。

像上文描述的這種每張表都有自己單獨(dú)的數(shù)據(jù)存儲文件的,叫獨(dú)占表空間;相對應(yīng)的,InnoDB 還有自己的系統(tǒng)表空間,在系統(tǒng)表空間下,所有表的數(shù)據(jù)都存儲在同一個(gè)文件中。

那數(shù)據(jù)什么時(shí)候存儲在系統(tǒng)表空間,又什么時(shí)候存儲在獨(dú)占表空間呢?

這個(gè)可以通過 MySQL 的配置項(xiàng) innodb_file_per_table 來決定。當(dāng)該配置項(xiàng)開啟時(shí),每張表都會有自己單獨(dú)的表空間;相反,當(dāng)該配置項(xiàng)關(guān)閉時(shí),表數(shù)據(jù)將會存儲在系統(tǒng)的表空間內(nèi)。

該配置項(xiàng)是默認(rèn)開啟的,你可以在 MySQL 中通過命令 SHOW VARIABLES LIKE 'innodb_file_per_table' 來查看該變量的狀態(tài)。

其實(shí)從 MySQL 將獨(dú)占表空間作為默認(rèn)的設(shè)置來看,你就應(yīng)該知道獨(dú)占表空間的性能肯定是要比系統(tǒng)表空間好的。

因?yàn)閷τ谙到y(tǒng)表空間來說,通常只有一個(gè)文件,所有的表數(shù)據(jù)都在這一個(gè)文件中,如果我們對某張表進(jìn)行 TRUNCATE 操作,需要將分散在文件中各個(gè)地方的數(shù)據(jù)刪除。首先這樣做性能就不好,其次 TRUNCATE 操作會在該文件中產(chǎn)生很多空閑的碎片空間,并且并不會減少共享表空間文件 ibdata1 的大小。

不能理解的話,可以想象 Java 里的標(biāo)記-清理垃圾回收算法,該算法會在清理的時(shí)候造成大量的內(nèi)存碎片,不利于提高后期的內(nèi)存利用率。

而對于獨(dú)占表空間來說,從始至終一整張表的數(shù)據(jù)都只存儲在一個(gè)文件,比起共享表空間誰更容易清理并且還能釋放磁盤空間,簡直是一目了然。所以,對于獨(dú)占表空間來說,TRUNCATE 的性能會更好。

除此之外,獨(dú)占表空間能夠提升單張表的最大容量限制,這塊可能不是很好理解,為什么獨(dú)占表空間還有這個(gè)功效?在這里你只需要記住這個(gè)結(jié)論就好了,后文講到頁相關(guān)的東西時(shí),我們會具體的論證。

了解了表空間的概念之后,我們就可以繼續(xù)深入了解數(shù)據(jù)在表空間內(nèi)到底是怎么存儲的了。

深入表空間文件內(nèi)部

其實(shí)在很早之前我講 InnoDB的內(nèi)存架構(gòu) 時(shí)我就講過,在 InnoDB 中,頁是其數(shù)據(jù)管理的最小單位。所以講道理我們應(yīng)該從其最小的部分開始,但是之前已經(jīng)專門寫過一篇文章來討論頁了,所以在這里就不贅述了。

表空間由一堆的**頁(Pages)**組成,并且每張頁的大小是相等的,頁大小默認(rèn)為 16K,當(dāng)然這個(gè)大小可以調(diào)整。

頁大小可以通過配置項(xiàng) innodb_page_size 根據(jù)業(yè)務(wù)的實(shí)際情況進(jìn)行調(diào)整,可以選擇的大小分別為 4K、8K、16K、32K和64K。

一堆頁組合在一起,就變成了區(qū)(Extents)。

每個(gè)區(qū)的大小是固定的。當(dāng)我們設(shè)置了不同的 innodb_page_size 時(shí),每個(gè)區(qū)(Extents)內(nèi)所包含的頁的數(shù)量、和對應(yīng)的固定區(qū)大小都不同,具體的情況如下圖所示。

當(dāng) innodb_page_size 為 4K、8K或者16K時(shí),其對應(yīng)的區(qū)(Extents)大小為1M;當(dāng)其頁大小為32K時(shí),區(qū)大小為2M;當(dāng)頁大小為64K時(shí),區(qū)大小為4M。

MySQL 5.6的時(shí)候其實(shí)只支持 4K、8K和16K,至于上面說到的32K和64K,是在 MySQL 5.7.6 之后添加的。

隨著頁和區(qū)大小的變動,每個(gè)區(qū)內(nèi)所能容納的 頁數(shù)量 也會隨之改變。舉個(gè)例子,當(dāng) innodb_page_size 的值為 16K 時(shí),每個(gè)區(qū)就包含 64 頁;當(dāng)其為 8K 時(shí),每個(gè)區(qū)包含 128 頁;當(dāng)其為 4K 時(shí),每個(gè)區(qū)就會包含 256 頁。

上面聊過,一頁一頁的數(shù)據(jù)組成了區(qū),而一個(gè)一個(gè)區(qū)則組成了段(Segments)。

在邏輯上,InnoDB 的表空間就是由一個(gè)一個(gè)這樣的段(Segment)組成的。隨著數(shù)據(jù)量的持續(xù)增長需要申請新的空間時(shí),InnoDB 會先請求32個(gè)頁,之后便會直接分配一整個(gè)區(qū)(Extents) 。甚至在某個(gè)很大的 Segment 內(nèi),還會一次性分配 4 個(gè)區(qū)。

默認(rèn)情況下,InnoDB 會給每個(gè)索引分配兩個(gè)段(Segment)。一個(gè)用于存儲索引中的非葉子結(jié)點(diǎn),另一個(gè)用于存儲葉子結(jié)點(diǎn)。

表空間的分類

上面大概介紹了兩種表空間類別,分別是系統(tǒng)表空間、獨(dú)占表空間。接下來就需要詳細(xì)的了解一下各個(gè)表空間分類的細(xì)節(jié)了。

系統(tǒng)表空間

當(dāng)我們開啟了innodb_file_per_table 這個(gè)配置項(xiàng)(默認(rèn)就是開啟的)之后,系統(tǒng)表空間內(nèi)就只用于存儲 Change Buffer 相關(guān)的數(shù)據(jù)。而當(dāng)我們將其關(guān)閉之后,系統(tǒng)表空間內(nèi)就會存儲表和索引相關(guān)的數(shù)據(jù)。當(dāng)然,在 MySQL 8.0之前,獨(dú)占表空間內(nèi)還包含了 Double Write Buffer(兩次寫緩沖),但在 MySQL 8.0.20 之后被移了出去,存放在了一個(gè)單獨(dú)的文件中。

默認(rèn)情況下,系統(tǒng)表空間只會有一個(gè)叫 ibdata1 的數(shù)據(jù)文件,當(dāng)然,它是允許有多個(gè)文件存在的。這所有的屬性包括文件名稱、文件大小都是通過配置項(xiàng)目 innodb_data_file_path 來制定的,舉個(gè)例子:

  1. innodb_data_file_path=ibdata1:10M:autoextend 

這里指明了系統(tǒng)表空間的文件名為ibdata1 ,初始化大小為10M 。你可發(fā)現(xiàn)了,這個(gè) autoextend 是個(gè)什么鬼?

剛剛說到,初始大小是 10M ,那么隨著 MySQL 的運(yùn)行,其數(shù)據(jù)量會慢慢的增長,數(shù)據(jù)文件必須要申請更多的空間來存儲數(shù)據(jù)。而定義了 autoextend InnoDB 就會幫我們自動對數(shù)據(jù)文件進(jìn)行擴(kuò)容,每次擴(kuò)容申請 8M 的空間。當(dāng)然,這個(gè) 8M 也是可以配置的,我們可以通過配置項(xiàng) innodb_autoextend_increment 來配置。

獨(dú)占表空間

這塊其實(shí)上面在引入的時(shí)候已經(jīng)介紹的差不多了,這里簡單的總結(jié)一下就好。當(dāng)配置項(xiàng) innodb_file_per_table 開啟時(shí)(現(xiàn)在是默認(rèn)開啟的),每張表的數(shù)據(jù)都會存儲自己單獨(dú)的數(shù)據(jù)文件中。

常規(guī)表空間

這個(gè)暫時(shí)不用了解,知道常規(guī)表空間跟系統(tǒng)表空間類似,也是一個(gè)共享的存儲空間就好。

Undo 表空間

這里主要存儲 Undo Logs,有了 Undo Logs 我們就可以在事務(wù)出錯(cuò)之后快速的將更改回滾。InnoDB 會默認(rèn)給 Undo 表空間創(chuàng)建兩個(gè)數(shù)據(jù)文件,如果沒有特別指定,其文件名默認(rèn)為 undo_001 和 undo_002 。

至于這兩個(gè)數(shù)據(jù)文件的具體存放路徑,可以通過配置項(xiàng) innodb_undo_directory 來指定。當(dāng)然,如果沒有指定,Undo 表空間的數(shù)據(jù)文件就會放在 InnoDB 的默認(rèn)數(shù)據(jù)目錄下,通常來說是 /usr/local/mysql 。

而這兩個(gè) Undo 表空間數(shù)據(jù)文件的初始大小,在 MySQL 8.0.23 之前是由 InnoDB 的頁大小來決定的,具體的情況如下圖:

而在 MySQL 8.0.23 之后,Undo 表空間的初始化大小都是 16M 了。至于 Undo 表空間的擴(kuò)容,不同的版本也有不通的處理方式。

在 MySQL 8.0.23 之前,每次擴(kuò)容是申請 4 個(gè)區(qū)(Extends),按照之前的討論,如果頁大小為 16 K,那么對應(yīng)到區(qū)就是 1M,換句話說,每次擴(kuò)容申請 4M 的空間,當(dāng)然這個(gè)具體的大小會根據(jù)頁大小的變化而變化,這個(gè)在上文提到過在此就不再贅述。

而在 MySQL 8.0.23 之后,每次最少都要擴(kuò)容 16 M的空間。而且,為了防止數(shù)據(jù)量爆發(fā)式的增長,InnoDB 對擴(kuò)容的容量會做一個(gè)動態(tài)的調(diào)整。

如果本次擴(kuò)容和上次擴(kuò)容時(shí)間差小于 0.1 秒,則擴(kuò)容的空間會加倍,也就是變成 32 M。如果多次擴(kuò)容的時(shí)間差都小于 0.1 秒,這個(gè) 加倍 的操作會 累加,直到達(dá)到上限 256M;那你可能會說,那如果某段時(shí)間剛好請求量比較大,使得擴(kuò)容的容量達(dá)到了最大的 256 M,那后續(xù)請求量下去了呢?難道還是申請 256 M嗎?這顯的不太合理。所以 InnoDB 判斷如果兩次擴(kuò)容間隔大于 0.1 秒,就會將擴(kuò)容的容量減半,直到減少到最小限制 16 M。

臨時(shí)表空間

臨時(shí)表空間內(nèi)的數(shù)據(jù),顧名思義都是臨時(shí)的。

你在說屁話...

它分為兩個(gè)部分,分別是:

  • Session 臨時(shí)表空間
  • 全局臨時(shí)表空間

對于 Session 臨時(shí)表空間,里面會存儲由用戶或者優(yōu)化器創(chuàng)建的臨時(shí)表。對于每個(gè) Session 來說,InnoDB 最多會分配兩個(gè)數(shù)據(jù)文件(表空間),分別用于存儲用戶創(chuàng)建的臨時(shí)表和優(yōu)化器創(chuàng)建的內(nèi)部臨時(shí)表。當(dāng) Session 失效之后,這些已分配的數(shù)據(jù)文件會被 Truncate 然后放到一個(gè) 數(shù)據(jù)文件池 中。

這個(gè)操作其實(shí)跟其他的池化技術(shù)沒有區(qū)別,值得注意的是,這些文件被 Truncate 了之后大小并不會發(fā)生變化。而這個(gè)數(shù)據(jù)文件池會在 MySQL 服務(wù)器啟動的時(shí)候創(chuàng)建,里面會默認(rèn)扔進(jìn)去 10 個(gè)文件,每個(gè)文件的默認(rèn)大小為 5 頁。

而對于全局臨時(shí)表空間,里面會存對臨時(shí)表做了改動的回滾段(Rollback Segment),其初始化的大小大約是 12 M,同樣會在 MySQL 服務(wù)器啟動的時(shí)候創(chuàng)建。

 

責(zé)任編輯:武曉燕 來源: SH的全棧筆記
相關(guān)推薦

2022-06-22 07:32:53

Sharding分庫數(shù)據(jù)源

2021-09-07 17:54:04

OpenGauss分區(qū)表索引

2022-08-29 08:00:11

哈希表數(shù)組存儲桶

2022-01-02 08:43:46

Python

2022-02-07 11:01:23

ZooKeeper

2023-01-03 08:31:54

Spring讀取器配置

2021-05-11 08:54:59

建造者模式設(shè)計(jì)

2021-07-05 22:11:38

MySQL體系架構(gòu)

2021-07-06 08:59:18

抽象工廠模式

2022-08-26 09:29:01

Kubernetes策略Master

2023-11-28 08:29:31

Rust內(nèi)存布局

2022-08-23 08:00:59

磁盤性能網(wǎng)絡(luò)

2021-07-02 08:51:29

源碼參數(shù)Thread

2021-07-16 22:43:10

Go并發(fā)Golang

2021-09-28 08:59:30

復(fù)原IP地址

2022-04-12 08:30:52

回調(diào)函數(shù)代碼調(diào)試

2021-10-27 09:59:35

存儲

2022-03-11 10:21:30

IO系統(tǒng)日志

2023-03-13 21:38:08

TCP數(shù)據(jù)IP地址

2023-11-01 09:07:01

Spring裝配源碼
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號