Innodb存儲(chǔ)表結(jié)構(gòu)

索引組織表(index organized table)

在InnoDB存儲(chǔ)引擎中,表都是根據(jù)主鍵順序組織存放的,這種存儲(chǔ)方式的表叫索引組織表。在InnoDB存在引擎表中,每張表都有個(gè)主鍵(Primary key),如果在創(chuàng)建表時(shí)沒(méi)有顯示定義主鍵,則會(huì)按照如下方式選擇或者創(chuàng)建主鍵:

(1) 判定是否有非空的唯一索引(unique not null),如果有則該列即為主鍵。若果有多個(gè),則選擇建表是第一個(gè)定義的非空位于索引為主鍵。注意:主鍵的選擇根據(jù)的是定義索引的順序,而不是建表時(shí)的列的順序。

(2) 如果不存在唯一索引,InnoDB存儲(chǔ)引擎字段創(chuàng)建一個(gè)6字節(jié)大小的指針(僅內(nèi)部可見(jiàn))。

InnoDB邏輯存儲(chǔ)結(jié)構(gòu)

在InnoDB存儲(chǔ)引擎中,所有的數(shù)據(jù)都被邏輯地存放在一個(gè)空間中,稱之為表空間(tablespace)。表空間又由段(segment)、區(qū)(extent)、頁(yè)(page)組成。InnoDB存儲(chǔ)引擎的邏輯存儲(chǔ)結(jié)構(gòu)如下圖。


表空間 可以看見(jiàn)InnoDB存儲(chǔ)引擎邏輯結(jié)構(gòu)的最高層,所有的數(shù)據(jù)都存放在表空間中。表空間又分為獨(dú)立表空間和共享表空間。通過(guò)參數(shù)innodb_file_per_table參數(shù)來(lái)決定使用何種類型的表空間。但是需要注意的是獨(dú)立表空間內(nèi)只存放數(shù)據(jù)、索引和插入緩沖頁(yè),其他的數(shù)據(jù),如回滾(undo)信息、插入緩沖索引頁(yè)、系統(tǒng)事務(wù)信息、二次寫緩沖(double write buffer)等還是放置在原來(lái)的共享表空間中。

表空間由各個(gè)段組成。常見(jiàn)的段有數(shù)據(jù)段、索引段、回滾段等。InnoDB存儲(chǔ)引擎是索引組織表,因此數(shù)據(jù)即索引,索引即數(shù)據(jù)。數(shù)據(jù)段即為B+樹(shù)的葉子點(diǎn)(leaf node segment),索引段為B+數(shù)據(jù)的非索引節(jié)點(diǎn)(non-leaf node segment)?;貪L段比較特殊以后在介紹。段都是引擎自身管理的。

區(qū) 區(qū)是由連續(xù)頁(yè)組成的空間。InnoDB存儲(chǔ)引擎頁(yè)的大小為16KB,一個(gè)區(qū)有64個(gè)連續(xù)的頁(yè)組成,所以每個(gè)區(qū)的大小都是1MB。參數(shù)innodb_page_size可設(shè)置頁(yè)的大小4K、8K,但是,不論頁(yè)的大小怎么變化,區(qū)的大小不變1M。但是有這樣一個(gè)問(wèn)題:在開(kāi)啟獨(dú)立表空間之后,創(chuàng)建的表默認(rèn)大小是96K,區(qū)中是64個(gè)連續(xù)的頁(yè),創(chuàng)建的表空間應(yīng)該是1M才對(duì)呀?這是因?yàn)樵诿總€(gè)段的開(kāi)始時(shí),先用32個(gè)頁(yè)大小的碎片頁(yè)(fragment page)來(lái)保存數(shù)據(jù),在使用完這些頁(yè)之后才是64個(gè)連續(xù)的頁(yè)的申請(qǐng)。這樣做是對(duì)于一些小表或者undo這類的段,可以在開(kāi)始時(shí)申請(qǐng)較少的空間,節(jié)省磁盤容量的開(kāi)銷。

頁(yè) 頁(yè)是InnoDB磁盤管理的最小單位。默認(rèn)大小為16K,可以通過(guò)innodb_page_size將頁(yè)的大小設(shè)置為4K、8K、16K,則所有表中頁(yè)的大小都為設(shè)置值,不可以對(duì)其再次修改。除非通過(guò)mysqldump導(dǎo)入和導(dǎo)出操作來(lái)產(chǎn)生新的庫(kù)。常見(jiàn)的頁(yè)的類型有:數(shù)據(jù)頁(yè)(B-tree Node)、undo頁(yè)(unod Log Page)、系統(tǒng)頁(yè)(System Page)、事務(wù)數(shù)據(jù)頁(yè)(Transaction system Page)、插入緩沖空閑列表頁(yè)(Insert Buffer Free List)、未壓縮的二進(jìn)制大對(duì)象頁(yè)(Uncompressed BLOB Page)、壓縮的二進(jìn)制對(duì)象頁(yè)(compressed BLOB Page)。

InnoDB存儲(chǔ)引擎是面向行的(row-oriented),也就是說(shuō)數(shù)據(jù)是按行進(jìn)行存放的。每個(gè)頁(yè)存放的行記錄也是有硬性定義的,最多運(yùn)行存放(16K/2-200)行的記錄,即7992行記錄。

InnoDB物理存儲(chǔ)結(jié)構(gòu)

InnoDB表由共享表空間(ibdata1),redo日志文件組(ib_logfile0,ib_logfile1),表結(jié)構(gòu)定義文件(表名.frm)組成。當(dāng)開(kāi)啟獨(dú)立表空間時(shí),還有以表名.ibd的文件,存儲(chǔ)數(shù)據(jù),索引,插入緩存列。

InnoDB行記錄格式

InnoDB存儲(chǔ)引擎的記錄是以行的形式存儲(chǔ)的,這就表明頁(yè)中保存著表中一行行的數(shù)據(jù)。其類型有REDUNDANT、 COMPACT、COMPRESS、DYNAMIC四種??梢酝ㄟ^(guò)show table status

COMPACT 在MySQL 5.0中引入,其設(shè)計(jì)目標(biāo)是高效的存儲(chǔ)數(shù)據(jù)。也就是一個(gè)頁(yè)中存放的行數(shù)據(jù)越多,其性能越高。compact行記錄的存放方式:

  1. 第一部分是一個(gè)非NULL變長(zhǎng)字段長(zhǎng)度列表(字節(jié)數(shù)與非NULL變長(zhǎng)字段數(shù)相同),且其是按列的順序逆序放置的。

  2. 第二部分是NULL標(biāo)志位(1個(gè)bit表示對(duì)應(yīng)列的NULL),該位指示了改行數(shù)據(jù)中是否有NULL值。

  3. 第三部分是記錄頭信息,固定占用5字節(jié)(40位),每位含義如下:

    最后的部分就是實(shí)際存儲(chǔ)每列的數(shù)據(jù)。

  • 需要注意的是:
  1. NULL除了占有NULL標(biāo)志位,實(shí)際存儲(chǔ)不占任何空間。
  2. 每行數(shù)據(jù)除了用戶定義的列之外,還有兩個(gè)隱藏列事務(wù)ID列回滾指針列。分別為6字節(jié)和7字節(jié)的大小。若InnoDB表沒(méi)有定義主鍵,每行還會(huì)增加一個(gè)6字節(jié)的rowid列。
  3. 固定長(zhǎng)度CHAR字段在未能完全占用其長(zhǎng)度空間時(shí),會(huì)用0x20來(lái)進(jìn)行填充。
  4. 記錄頭信息的最后兩個(gè)字節(jié)代表next_recorder,代表下一條記錄的偏移量,所以InnoDB在頁(yè)內(nèi)部是通過(guò)一種鏈表的結(jié)構(gòu)來(lái)串連各個(gè)行記錄的。

REDUNDANT

  • Redundant是MySQL5.0版本之前InnoDB的行記錄格式,其存在是為了兼容老版本的頁(yè)格式。
  • Redundant行記錄存儲(chǔ)方式:
  1. 第一部分是一個(gè)字段長(zhǎng)度偏移列表,同樣是按列的順序逆序放置的。

  2. 第二部分是記錄頭信息,不同于Compact,Redundant占用6字節(jié)(48位),每位含義如下:


  1. 其中n_fields值代表一行中列的數(shù)量,占用10位。這也解釋了為什么MySQL 一行支持的最多列數(shù)為1023。
  • 需要注意的是:
  1. 對(duì)于NULL值的處理,Redundant和Compact非常不同:對(duì)于VARCHAR類型的NULL值,Redundant同樣不占用任何空間,但CHAR類型的NULL值需要占用最大值字節(jié)數(shù)大小的空間。

行溢出數(shù)據(jù)

  • InnoDB可以將一條記錄中的某些數(shù)據(jù)存儲(chǔ)在真正的數(shù)據(jù)頁(yè)面之外。

  • 是否溢出與列類型是否為BLOB等大對(duì)象列類型并無(wú)直接關(guān)系。而是根據(jù)“保證一個(gè)頁(yè)至少能存放兩條記錄”的標(biāo)準(zhǔn)來(lái)判斷的。如果VERCHAR類型的列長(zhǎng)度過(guò)長(zhǎng)導(dǎo)致一頁(yè)只能存儲(chǔ)一條記錄,則也會(huì)被放到Uncompressed BLOB Page(行溢出數(shù)據(jù)頁(yè))。之所以有這個(gè)標(biāo)準(zhǔn)的原因,是因?yàn)槿绻荒鼙WC如此,那B+Tree就是去意義變成鏈表了。

  • InnoDB能存放VARCHAR類型的最大長(zhǎng)度為65532字節(jié) (注意并非65535,這其中還有其他開(kāi)銷)。另外要注意,VARCHAR(N)中的N指的是字符的長(zhǎng)度而非字節(jié)。另外,MySQL手冊(cè)中定義的65535字節(jié)長(zhǎng)度是指所有VARCHAR列的長(zhǎng)度總和。

  • 當(dāng)發(fā)生行溢出時(shí),數(shù)據(jù)頁(yè)中值保存了列的前768字節(jié)的前綴數(shù)據(jù),之后是偏移量,指向行溢出頁(yè)。如下圖所示:

Compressed和Dynamic行記錄格式

  • 從InnoDB1.0.x開(kāi)始引入了新的文件格式(可理解為頁(yè)格式):Barracuda。而之前的文件格式被稱為Antelope,Barracuda包含了Antelope:
  • 新的兩種行記錄格式對(duì)于存放在BLOB中的數(shù)據(jù)采用了完全的行溢出方式,在數(shù)據(jù)頁(yè)中只存放20個(gè)字節(jié)的指針,實(shí)際的數(shù)據(jù)都存放子頁(yè)OffPage中。而之前的兩種行記錄格式都是會(huì)存放768個(gè)前綴字節(jié)。新的行溢出方式如下:
  • Compressed行記錄格式的另一個(gè)功能就是:存儲(chǔ)在其中的行數(shù)據(jù)會(huì)以zlib的算法進(jìn)行壓縮。因此對(duì)于BLOB、TEXT、VARXCHAR這些大長(zhǎng)度類型的數(shù)據(jù)能夠非常有效的存儲(chǔ)。

CHAR的行存儲(chǔ)結(jié)構(gòu)

  • 從MySQL4.1開(kāi)始,CHAR(N)中的N指的是字符的長(zhǎng)度,而不是之前版本的字節(jié)長(zhǎng)度。也就是說(shuō)在不同字符集下,CHAR類型列內(nèi)部存儲(chǔ)的可能不是定長(zhǎng)的數(shù)據(jù)。
  • 另外由于多字節(jié)的字符編碼,不同字符的長(zhǎng)度可能不同,所以CHAR類型不再代表固定長(zhǎng)度的字符串了。因此,對(duì)于多字節(jié)字符編碼的CHAR類型的存儲(chǔ),InnoDB在內(nèi)部將其視為變長(zhǎng)字符類型。這也就意味著在變長(zhǎng)長(zhǎng)度列表中會(huì)記錄CHAR數(shù)據(jù)類型的長(zhǎng)度。只是對(duì)于未能占滿長(zhǎng)度的字符還是填充0x20。

InnoDB數(shù)據(jù)頁(yè)結(jié)構(gòu)

  • 通過(guò)前面內(nèi)容我們已了解到,頁(yè)是InnoDB管理數(shù)據(jù)庫(kù)的最小磁盤單位。
  • InnoDB數(shù)據(jù)頁(yè)由以下七部分組成:

File Header:文件頭

  • File Header用來(lái)記錄頁(yè)的一些頭信息,共由如下8部分組成,共占用38字節(jié):
  • InnoDB頁(yè)的類型:

Page Header:頁(yè)頭

  • 該部分用來(lái)記錄數(shù)據(jù)頁(yè)的狀態(tài)信息,由14個(gè)部分組成,共56字節(jié):

Infimum和Supremum Record

  • 在InnoDB中,每個(gè)數(shù)據(jù)頁(yè)都有兩個(gè)虛擬的行記錄,用來(lái)限定記錄的邊界。
  • Infimum記錄是比該頁(yè)中任何主鍵值都要小的值。
  • Supremum指比任何可能大的值還要大的值。
  • 這兩個(gè)值在頁(yè)創(chuàng)建時(shí)被建立,且在任何情況下都不會(huì)被刪除。
  • 示意圖如下:

User Record和Free Space

  • User Record,用戶記錄,即行記錄。
  • Free Space,空閑空間。是個(gè)鏈表數(shù)據(jù)結(jié)構(gòu)。在一條記錄被刪除后,該空間會(huì)被加入到空閑鏈表中。

Page Directory:頁(yè)目錄

  • Page Directory中存放了記錄的相對(duì)位置,有時(shí)將這些記錄指針?lè)Q為Slots()或Directory Slots(目錄槽)。
  • InnoDB中并不是每個(gè)記錄都擁有一個(gè)槽,InnoDB的槽是一個(gè)稀疏目錄,即一個(gè)槽中可能包含多個(gè)記錄。當(dāng)記錄被插入或刪除時(shí),需要對(duì)槽進(jìn)行分裂或平衡的維護(hù)操作。
  • 在槽中記錄按照索引鍵信息順序存放,這樣可以利用二叉查找迅速找到記錄的指針。
  • 需要注意的是:B+樹(shù)索引本身并不能找到具體的一條記錄,能找到只是該記錄所在的頁(yè)。數(shù)據(jù)庫(kù)把頁(yè)載入到內(nèi)存,然后通過(guò)Page Directory再進(jìn)行二叉查找。只不過(guò)二叉查找的時(shí)間復(fù)雜度很低,同時(shí)在內(nèi)存中查找很快,因此通常忽略這部分時(shí)間。

File Trailer:文件結(jié)尾信息

  • 為了檢測(cè)頁(yè)是否已完整地寫入磁盤(如寫入時(shí)可能發(fā)生磁盤損壞、機(jī)器關(guān)機(jī)等),InnoDB設(shè)置了File Trailer來(lái)保證頁(yè)的完整性

MySQL-InnoDB表 - 簡(jiǎn)書(shū)

MySQL InnoDB存儲(chǔ)引擎之表(一)_chenlvzhou的專欄-CSDN博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容