MySQL底層概述—4.InnoDB資料檔案

东阳马生架构發表於2024-11-29

大綱

1.表空間檔案結構

(1)表空間Tablesapce

(2)段Segment

(3)區Extend

(4)頁Page

(5)行Row

2.Page結構

(1)頁結構各部分說明

(2)頁結構整體劃分

3.行記錄格式

(1)行格式分類

(2)COMPACT行記錄格式

(3)Compact中的行溢位機制

(4)其他行格式記錄

1.表空間檔案結構

(1)表空間Tablesapce

(2)段Segment

(3)區Extend

(4)頁Page

(5)行Row

InnoDB表空間檔案結構從邏輯上可以分為:

Tablespace(表空間)->Segment(段)->

Extent(區)->Page(頁)->Row(行)

一個表空間會包含多個段,一個段會包含多個區(256個區就是一個組);一個區又會包含64個頁,一個頁裡面又會包含一行一行的記錄Row。

MySQL底層概述—4.InnoDB資料檔案

(1)表空間Tablesapce

一.表空間是什麼

表空間能夠看作是InnoDB儲存引擎邏輯結構的最高層。表空間用於儲存多個ibd資料檔案,用於儲存表的記錄和索引。一個表空間檔案可以包含多個段:葉子節點段、非葉子節點段、回滾段。

二.表空間型別

系統表空間、獨佔表空間、通用表空間、 臨時表空間、Undo表空間。

(2)段Segment

一.段是什麼

段是用來管理空間的申請以及將同類的區和頁用連結串列管理起來。段是個邏輯概念,本質上是由若干個零散頁面和若干個完整的區組成。段是為了保持葉子節點在磁碟上的連續,可以實現更好的順序IO操作。

一個B+樹索引被劃分為兩個段:一個葉子節點段和一個非葉子節點段。這樣葉子節點就可以儘可能地存放在一起,非葉子節點也可以儘可能地存放在一起。

二.段的型別

常見的段有資料段、索引段、回滾段等。其中索引段就是非葉子節點部分,而資料段就是葉子節點部分,回滾段用於資料的回滾和多版本控制。

三.為什麼引入段

原因一:

使用B+樹執行查詢時只是掃描葉子節點記錄,如果不區分葉子節點和非葉子節點,通通把節點代表的頁面放到申請的區中,那麼掃描效果就大打折扣,而段可以讓葉子節點的資料頁儘可能連續和差距不那麼大。所以InnoDB對B+樹的葉子節點和非葉子節點進行區別對待,葉子節點和非葉子節點各有自己獨有的區。而存放葉子節點的區的集合就算是一個段,存放非葉子節點的區的集合也算是一個段。即一個索引會生成兩個段:一個葉子節點段和一個非葉子節點段。

原因二:

以完整的區為單位分配給某個段時,對於資料量較小的表來說太浪費儲存空間。因為當段以區為單位申請儲存空間時,由於一個區預設佔用1MB儲存空間以及一個聚簇索引會生成兩個段,所以預設情況下只存放了幾條記錄的小表也需要2MB的儲存空間,這就有點浪費了。

原因三:

出現上述問題的根源是:區中的所有頁面都是為了儲存同一個段的資料而存在,即使區的頁面用不完也不能作他用。於是InnoDB便有了碎片區。在一個碎片區中,並非所有頁都是為了儲存同一個段的資料而存在,碎片區的頁可以用於不同的目的的。比如有些頁屬於段A、有些頁屬於段B、有些頁甚至不屬於任何段。碎片區直屬於表空間,不屬於任何段。

原因四:

為某個段分配儲存空間的策略:剛開始向表中插入資料時,段是從某個碎片區中以單個頁面為單位來分配儲存空間的。當某個段已經佔用了32個碎片區頁面後,就會以完整的區為單位來分配儲存空間,原先佔用的碎片區頁面並不會被複制到新申請的完整的區中。所以說,段是一些零散的頁面以及一些完整的區的集合。

(3)區Extend

一.區是什麼

區由連續頁組成的空間,一個區的大小是1M,一個區有64個連續的頁。為了保證區中頁的連續性,擴充套件時InnoDB一次從磁碟申請4~5個區。無論是系統表空間還是獨立表空間,都可看成是由若干個連續的區組成。當一個段使用了32個碎片頁後才是以區來分配,每256個區被分成一組。

二.為什麼引入區

原因一:

向表中插入一條記錄,本質上就是向該表的聚簇索引以及所有二級索引代表的B+樹的節點中插入資料。而B+樹每一層中的頁都會形成一個雙向連結串列,如果以頁為單位來分配儲存空間,那麼雙向連結串列中相鄰的兩個頁之間的物理位置可能離得非常遠。

原因二:

使用B+樹來減少記錄的掃描行數的過程是:透過一些搜尋條件,到B+樹的葉子節點中定位到第一條符合該條件的記錄,然後沿著由記錄組成的單向連結串列以及由資料頁組成的雙向連結串列,一直向後進行掃描。全表掃描就是定位到第一個葉子節點的第一條記錄。

原因三:

如果雙向連結串列中相鄰的兩個頁的物理位置不連續,對於傳統的機械硬碟來說,需要重新定位磁頭位置,也就是會產生隨機IO,影響效能。所以應儘量讓頁面連結串列中相鄰的頁的物理位置也相鄰,以便掃描葉子節點的大量記錄時可以使用順序IO。

原因四:

為了儘量消除隨機IO才引入了區的概念,一個區就是物理位置上連續的64個頁,區中頁面的頁號都是連續的。當表中的資料量很大時,為某個索引分配空間時就不再按頁為單位來分配了,而是按照區為單位進行分配。甚至當表中的資料非常非常多的時候,可以一次性分配多個連續的區,以消除更多的隨機IO,但會造成一點空間的浪費。

(4)頁Page

一.頁是什麼

區是由連續的頁(Page)組成的空間,一個頁的儲存大小為16K,頁用於儲存多個Row行記錄。

二.頁的型別

頁有很多種型別,如資料頁、Undo頁、系統頁、事務資料頁、大的BLOB物件頁。

(5)行Row

InnoDB的資料是按行的方式進行存放的,每個頁存放的行記錄最多允許存放16K / 2 - 200行的記錄,即每個頁最多存放7992行記錄。每行記錄根據不同的行格式、不同的資料型別,會有不同的儲存方式。

行包含的內容:記錄的欄位值、事務ID、回滾指標、欄位指標等資訊。

2.Page結構

(1)頁結構各部分說明

(2)頁結構整體劃分

Page是InnoDB儲存的最基本構件,也是InnoDB磁碟管理的最小單位,與資料庫相關的所有內容都儲存在這種Page結構裡。

Page分為幾種型別,常見的頁型別有:資料頁(B+Tree Node)、Undo頁(Undo Log Page)、系統頁(System Page)、事務資料頁(Transaction System Page)等。

(1)頁結構各部分說明

MySQL底層概述—4.InnoDB資料檔案

一.File Header欄位用於記錄Page的頭資訊

其中比較重要的是FIL_PAGE_PREV和FIL_PAGE_NEXT欄位。透過這兩個欄位,就可以找到該頁的上一頁和下一頁。實際上所有頁透過兩個欄位可以形成一條雙向連結串列。

二.Page Header欄位用於記錄Page的狀態資訊

三.Infimum和Supremum是最小和最大行記錄

Infimum(下確界)記錄比該頁中任何主鍵值都要小的值,Supremum(上確界)記錄比該頁中任何主鍵值都要大的值,這兩個偽記錄構成了頁中記錄的邊界。

四.User Records存放的是實際的資料行記錄

五.Free Space中存放的是空閒空間

被刪除的行記錄會成為空閒空間。

六.Page Directory記錄與二叉查詢相關的資訊

七.File Trailer儲存檢測資料完整性的資料

(2)頁結構整體劃分

頁結構整體上可以分為三大部分,分別為:通用部分(檔案頭、檔案尾)、資料記錄部分、頁目錄部分。

一.通用部分(File Header&File Trailer)

通用部分主要指檔案頭和檔案尾,將頁的內容進行封裝。透過檔案頭和檔案尾校驗的CheckSum方式可以確保頁的傳輸是完整的,這時候就可以確認是否發生頁斷裂也就是頁是否寫失效了。

其中比較重要的是在檔案頭中的FIL_PAGE_PREV和FIL_PAGE_NEXT欄位,透過這兩個欄位,可以找到該頁的上一頁和下一頁,因此所有頁可以形成一條雙向連結串列。

二.資料記錄部分(User Records&Free Space)

由於頁的主要作用是儲存記錄,所以"最小和最大記錄"和"使用者記錄"部分佔了頁結構的主要空間。另外空閒空間是個靈活的部分,當有新的記錄插入時,會從空閒空間中進行分配用於儲存新記錄。

MySQL底層概述—4.InnoDB資料檔案

三.頁目錄部分(Page Directory)

資料頁中的行記錄會按照主鍵值由小到大順序串聯成一個單連結串列,單連結串列的連結串列頭為最小記錄,連結串列尾為最大記錄。資料頁目錄中會順序儲存每一條行記錄的地址,透過對資料頁目錄使用二分法,就能快速定位到查詢的行記錄。

MySQL底層概述—4.InnoDB資料檔案

3.行記錄格式

(1)行格式分類

(2)COMPACT行記錄格式

(3)Compact中的行溢位機制

(4)其他行格式記錄

(1)行格式分類

表的行格式決定了它的行是如何物理儲存的,這反過來又會影響查詢和DML操作的效能。

如果在單個Page頁中容納更多行,那麼查詢和索引查詢就能更快地工作,並且緩衝池中所需的記憶體會更少,寫入更新時所需的IO也會更少。

InnoDB儲存引擎支援四種行格式:Redundant、Compact、Dynamic和Compressed。

下面查詢MySQL使用的行格式,MySQL5.7後預設是dynamic。

mysql> show variables like 'innodb_default_row_format';
+---------------------------+---------+
| Variable_name             | Value   |
+---------------------------+---------+
| innodb_default_row_format | dynamic |
+---------------------------+---------+

下面是指定行格式的語法:

CREATE TABLE <table_name(column_name)> ROW_FORMAT=行格式名稱
ALTER TABLE <table_name> ROW_FORMAT=行格式名稱

(2)COMPACT行記錄格式

Compact設計目標是高效地儲存資料,一個頁中存放的行資料越多,其效能就越高。Compact行記錄由兩部分組成:記錄的額外資訊和記錄的真實資料。

MySQL底層概述—4.InnoDB資料檔案

一.記錄額外資訊部分

伺服器為了描述一條記錄而新增了一些額外資訊(後設資料資訊),這些額外資訊分為3類,分別是:變長欄位長度列表、NULL值列表和記錄頭資訊。

第一類:變長欄位長度列表

MySQL支援一些變長的資料型別,比如VARCHAR(M)、VARBINARY(M)、各種TEXT型別,各種BLOB型別。這些變長的資料型別佔用的儲存空間分兩部分:真正的資料內容和佔用的位元組數。

變長欄位的長度是不固定的,所以在儲存資料時要把這些資料佔用的位元組數也存起來。讀取資料時才能根據這個長度列表去讀取對應長度的資料。

在Compact行格式中:會把所有變長型別的列的長度都存放在記錄的開頭部位形成一個列表,按照列的順序逆序存放,這個列表就是變長欄位長度列表。

第二類:NULL值列表

表中的某些列可能會儲存NULL值,如果把這些NULL值都放到記錄的真實資料中會比較浪費空間,所以Compact行格式把這些值為NULL的列儲存到NULL值列表中。如果表中所有列都不允許為 NULL,就不存在NULL值列表。

第三類:記錄頭資訊

記錄頭資訊是由固定的5個位元組組成,5個位元組也就是40個二進位制位,不同的位代表不同的意思。

MySQL底層概述—4.InnoDB資料檔案

delete_mask:這個屬性標記著當前記錄是否被刪除,佔用1個二進位制位。值為0時代表記錄並沒有被刪除,值為1時代表記錄被刪除掉。

min_rec_mask:標記該記錄是否是B+樹的每層非葉子節點中的最小記錄。

n_owned:代表每個分組裡,所擁有的記錄的數量,一般是分組裡主鍵最大值才有的。

heap_no:在資料頁的User Records中插入的記錄是一條條緊湊排列的,這種緊湊排列的結構又被稱為堆。為了便於管理這個堆,把記錄在堆中的相對位置給定一個編號heap_no,所以heap_no這個屬性表示當前記錄在本頁中的位置。

record_type:這個屬性表示當前記錄的型別,一共有4種型別的記錄。0表示普通使用者記錄、1表示B+樹非葉節點記錄、2表示最小記錄、3表示最大記錄。

next_record :表示從當前記錄的真實資料到下一條記錄的真實資料的地址偏移量,可以理解為指向下一條記錄地址的指標。值為正數說明下一條記錄在當前記錄的後面,值為負數說明下一條記錄在當前記錄的前面。

二.記錄真實資料部分

除了記錄真實資料以外,MySQL還會為每條行記錄新增一些列。這些列被稱為隱藏列,具體的列如下:

MySQL底層概述—4.InnoDB資料檔案

列說明如下:

MySQL底層概述—4.InnoDB資料檔案

生成隱藏主鍵列的步驟:

步驟一:

伺服器會在記憶體中維護一個全域性變數,每當向某個包含隱藏的row_id列的表中插入一條記錄時,就會把該變數的值當作新記錄的row_id列的值,並且把該變數自增1。

步驟二:

每當這個變數的值為256的倍數時,就會將該變數的值,重新整理到系統表空間的頁號為7的頁面中一個Max Row ID的屬性中。

步驟三:

系統啟動時會將頁中的Max Row ID屬性載入到記憶體中,並將該值加上256後賦值給全域性變數,因為在上次關機時該全域性變數的值可能大於頁中Max Row ID屬性值。

(3)Compact中的行溢位機制

一.什麼是行溢位

MySQL中是以頁為基本單位進行磁碟與記憶體之間的資料互動的。一個頁的大小是16K = 16384位元組。一個varchar(m)型別列最多可以儲存65532個位元組,一些大的資料型別如TEXT可以儲存更多。如果一個表存在這樣的大欄位,那麼一個頁就無法儲存一條完整的記錄。這時就會發生行溢位,多出的資料就會儲存在另外的溢位頁中。

總結:如果某些欄位資訊過長,無法儲存在B樹節點中。這時候會被單獨分配空間,此時被稱為溢位頁,該欄位被稱為頁外列。

二.Compact中的行溢位機制

InnoDB規定一頁至少儲存兩條記錄(B+樹特點),如果頁中只能存放下一條記錄,InnoDB會自動將行資料存放到溢位頁中。當發生行溢位時,資料頁只儲存前768位元組的字首資料,接著是20個位元組的偏移量,指向行溢位頁。

MySQL底層概述—4.InnoDB資料檔案

(4)其他行格式記錄

一.DYNAMIC和COMPRESSED行記錄格式

DYNAMIC和COMPRESSED新格式引入的功能有:資料壓縮、增強型長列資料的頁外儲存和大索引字首。

Compressed和Dynamic行記錄格式與Compact行記錄格式是類似的。區別是在處理行溢位時:資料頁不會儲存真實資料的前768位元組(完全溢位),而只儲存20個位元組的指標來指向溢位頁。

MySQL底層概述—4.InnoDB資料檔案

Compressed與Dynamic相比:Compressed儲存的行資料會以zlib的演算法進行壓縮以節省空間,因此對於BLOB、TEXT、VARCHAR這類大長度型別的資料能有效儲存。MySQL5.7預設的行記錄格式是Dynamic。

二.Redundant

Redundant是MySQL 5.0版本前InnoDB的行記錄儲存方式。Redundant行記錄的格式是:首部是一個欄位長度偏移列表,同樣是按照列的順序逆序放置的,該條記錄中所有列的長度資訊都按照逆序儲存到欄位長度偏移列表,這些列當然包括隱藏列、NULL值列等。

MySQL底層概述—4.InnoDB資料檔案

相關文章