索引組織表(index organized table)
在InnoDB存儲引擎中,表都是根據(jù)主鍵順序組織存放的,這種存儲方式的表叫索引組織表酥泞。在InnoDB存在引擎表中,每張表都有個主鍵(Primary key)啃憎,如果在創(chuàng)建表時沒有顯示定義主鍵芝囤,則會按照如下方式選擇或者創(chuàng)建主鍵:
(1) 判定是否有非空的唯一索引(unique not null),如果有則該列即為主鍵辛萍。若果有多個悯姊,則選擇建表是第一個定義的非空位于索引為主鍵。注意:主鍵的選擇根據(jù)的是定義索引的順序贩毕,而不是建表時的列的順序悯许。
(2) 如果不存在唯一索引,InnoDB存儲引擎字段創(chuàng)建一個6字節(jié)大小的指針(僅內(nèi)部可見)辉阶。
InnoDB邏輯存儲結(jié)構(gòu)
在InnoDB存儲引擎中先壕,所有的數(shù)據(jù)都被邏輯地存放在一個空間中,稱之為表空間(tablespace)睛藻。表空間又由段(segment)启上、區(qū)(extent)、頁(page)組成店印。InnoDB存儲引擎的邏輯存儲結(jié)構(gòu)如下圖冈在。
表空間 可以看見InnoDB存儲引擎邏輯結(jié)構(gòu)的最高層,所有的數(shù)據(jù)都存放在表空間中按摘。表空間又分為獨立表空間和共享表空間包券。通過參數(shù)innodb_file_per_table參數(shù)來決定使用何種類型的表空間纫谅。但是需要注意的是獨立表空間內(nèi)只存放數(shù)據(jù)、索引和插入緩沖頁溅固,其他的數(shù)據(jù)付秕,如回滾(undo)信息、插入緩沖索引頁侍郭、系統(tǒng)事務(wù)信息询吴、二次寫緩沖(double write buffer)等還是放置在原來的共享表空間中。
段 表空間由各個段組成亮元。常見的段有數(shù)據(jù)段猛计、索引段、回滾段等爆捞。InnoDB存儲引擎是索引組織表奉瘤,因此數(shù)據(jù)即索引,索引即數(shù)據(jù)煮甥。數(shù)據(jù)段即為B+樹的葉子點(leaf node segment)盗温,索引段為B+數(shù)據(jù)的非索引節(jié)點(non-leaf node segment)〕芍猓回滾段比較特殊以后在介紹卖局。段都是引擎自身管理的。
區(qū) 區(qū)是由連續(xù)頁組成的空間艇劫。InnoDB存儲引擎頁的大小為16KB吼驶,一個區(qū)有64個連續(xù)的頁組成,所以每個區(qū)的大小都是1MB店煞。參數(shù)innodb_page_size可設(shè)置頁的大小4K、8K风钻,但是顷蟀,不論頁的大小怎么變化,區(qū)的大小不變1M骡技。但是有這樣一個問題:在開啟獨立表空間之后鸣个,創(chuàng)建的表默認(rèn)大小是96K,區(qū)中是64個連續(xù)的頁布朦,創(chuàng)建的表空間應(yīng)該是1M才對呀囤萤?這是因為在每個段的開始時,先用32個頁大小的碎片頁(fragment page)來保存數(shù)據(jù)是趴,在使用完這些頁之后才是64個連續(xù)的頁的申請涛舍。這樣做是對于一些小表或者undo這類的段,可以在開始時申請較少的空間唆途,節(jié)省磁盤容量的開銷富雅。
頁 頁是InnoDB磁盤管理的最小單位掸驱。默認(rèn)大小為16K,可以通過innodb_page_size將頁的大小設(shè)置為4K没佑、8K毕贼、16K,則所有表中頁的大小都為設(shè)置值蛤奢,不可以對其再次修改鬼癣。除非通過mysqldump導(dǎo)入和導(dǎo)出操作來產(chǎn)生新的庫。常見的頁的類型有:數(shù)據(jù)頁(B-tree Node)啤贩、undo頁(unod Log Page)待秃、系統(tǒng)頁(System Page)、事務(wù)數(shù)據(jù)頁(Transaction system Page)瓜晤、插入緩沖空閑列表頁(Insert Buffer Free List)锥余、未壓縮的二進制大對象頁(Uncompressed BLOB Page)、壓縮的二進制對象頁(compressed BLOB Page)痢掠。
行 InnoDB存儲引擎是面向行的(row-oriented)驱犹,也就是說數(shù)據(jù)是按行進行存放的。每個頁存放的行記錄也是有硬性定義的足画,最多運行存放(16K/2-200)行的記錄雄驹,即7992行記錄。
InnoDB物理存儲結(jié)構(gòu)
InnoDB表由共享表空間(ibdata1)淹辞,redo日志文件組(ib_logfile0医舆,ib_logfile1),表結(jié)構(gòu)定義文件(表名.frm)組成象缀。當(dāng)開啟獨立表空間時蔬将,還有以表名.ibd的文件,存儲數(shù)據(jù)央星,索引霞怀,插入緩存列。
InnoDB行記錄格式
InnoDB存儲引擎的記錄是以行的形式存儲的莉给,這就表明頁中保存著表中一行行的數(shù)據(jù)毙石。其類型有REDUNDANT、 COMPACT颓遏、COMPRESS徐矩、DYNAMIC四種∪保可以通過show table status滤灯。
COMPACT 在MySQL 5.0中引入,其設(shè)計目標(biāo)是高效的存儲數(shù)據(jù)。也就是一個頁中存放的行數(shù)據(jù)越多力喷,其性能越高刽漂。compact行記錄的存放方式:
第一部分是一個非NULL變長字段長度列表(字節(jié)數(shù)與非NULL變長字段數(shù)相同),且其是按列的順序逆序放置的弟孟。
第二部分是NULL標(biāo)志位(1個bit表示對應(yīng)列的NULL)贝咙,該位指示了改行數(shù)據(jù)中是否有NULL值。
-
第三部分是記錄頭信息拂募,固定占用5字節(jié)(40位)庭猩,每位含義如下:
最后的部分就是實際存儲每列的數(shù)據(jù)。
- 需要注意的是:
- NULL除了占有NULL標(biāo)志位陈症,實際存儲不占任何空間蔼水。
- 每行數(shù)據(jù)除了用戶定義的列之外,還有兩個隱藏列录肯,事務(wù)ID列和回滾指針列趴腋。分別為6字節(jié)和7字節(jié)的大小。若InnoDB表沒有定義主鍵论咏,每行還會增加一個6字節(jié)的rowid列优炬。
- 固定長度CHAR字段在未能完全占用其長度空間時,會用0x20來進行填充厅贪。
- 記錄頭信息的最后兩個字節(jié)代表next_recorder蠢护,代表下一條記錄的偏移量,所以InnoDB在頁內(nèi)部是通過一種鏈表的結(jié)構(gòu)來串連各個行記錄的养涮。
REDUNDANT
- Redundant是MySQL5.0版本之前InnoDB的行記錄格式葵硕,其存在是為了兼容老版本的頁格式。
- Redundant行記錄存儲方式:
第一部分是一個字段長度偏移列表贯吓,同樣是按列的順序逆序放置的懈凹。
-
第二部分是記錄頭信息,不同于Compact悄谐,Redundant占用6字節(jié)(48位)蘸劈,每位含義如下:
- 其中n_fields值代表一行中列的數(shù)量,占用10位尊沸。這也解釋了為什么MySQL 一行支持的最多列數(shù)為1023。
- 需要注意的是:
- 對于NULL值的處理贤惯,Redundant和Compact非常不同:對于VARCHAR類型的NULL值洼专,Redundant同樣不占用任何空間,但CHAR類型的NULL值需要占用最大值字節(jié)數(shù)大小的空間孵构。
行溢出數(shù)據(jù)
InnoDB可以將一條記錄中的某些數(shù)據(jù)存儲在真正的數(shù)據(jù)頁面之外屁商。
是否溢出與列類型是否為BLOB等大對象列類型并無直接關(guān)系。而是根據(jù)“保證一個頁至少能存放兩條記錄”的標(biāo)準(zhǔn)來判斷的。如果VERCHAR類型的列長度過長導(dǎo)致一頁只能存儲一條記錄蜡镶,則也會被放到Uncompressed BLOB Page(行溢出數(shù)據(jù)頁)雾袱。之所以有這個標(biāo)準(zhǔn)的原因,是因為如果不能保證如此官还,那B+Tree就是去意義變成鏈表了芹橡。
InnoDB能存放VARCHAR類型的最大長度為65532字節(jié) (注意并非65535,這其中還有其他開銷)望伦。另外要注意林说,VARCHAR(N)中的N指的是字符的長度而非字節(jié)。另外屯伞,MySQL手冊中定義的65535字節(jié)長度是指所有VARCHAR列的長度總和腿箩。
當(dāng)發(fā)生行溢出時,數(shù)據(jù)頁中值保存了列的前768字節(jié)的前綴數(shù)據(jù)劣摇,之后是偏移量珠移,指向行溢出頁。如下圖所示:
Compressed和Dynamic行記錄格式
- 從InnoDB1.0.x開始引入了新的文件格式(可理解為頁格式):Barracuda末融。而之前的文件格式被稱為Antelope钧惧,Barracuda包含了Antelope:
- 新的兩種行記錄格式對于存放在BLOB中的數(shù)據(jù)采用了完全的行溢出方式,在數(shù)據(jù)頁中只存放20個字節(jié)的指針滑潘,實際的數(shù)據(jù)都存放子頁OffPage中垢乙。而之前的兩種行記錄格式都是會存放768個前綴字節(jié)。新的行溢出方式如下:
- Compressed行記錄格式的另一個功能就是:存儲在其中的行數(shù)據(jù)會以zlib的算法進行壓縮语卤。因此對于BLOB追逮、TEXT、VARXCHAR這些大長度類型的數(shù)據(jù)能夠非常有效的存儲粹舵。
CHAR的行存儲結(jié)構(gòu)
- 從MySQL4.1開始钮孵,CHAR(N)中的N指的是字符的長度,而不是之前版本的字節(jié)長度眼滤。也就是說在不同字符集下巴席,CHAR類型列內(nèi)部存儲的可能不是定長的數(shù)據(jù)。
- 另外由于多字節(jié)的字符編碼诅需,不同字符的長度可能不同漾唉,所以CHAR類型不再代表固定長度的字符串了。因此堰塌,對于多字節(jié)字符編碼的CHAR類型的存儲赵刑,InnoDB在內(nèi)部將其視為變長字符類型。這也就意味著在變長長度列表中會記錄CHAR數(shù)據(jù)類型的長度场刑。只是對于未能占滿長度的字符還是填充0x20般此。
InnoDB數(shù)據(jù)頁結(jié)構(gòu)
- 通過前面內(nèi)容我們已了解到,頁是InnoDB管理數(shù)據(jù)庫的最小磁盤單位。
- InnoDB數(shù)據(jù)頁由以下七部分組成:
File Header:文件頭
- File Header用來記錄頁的一些頭信息铐懊,共由如下8部分組成邀桑,共占用38字節(jié):
- InnoDB頁的類型:
Page Header:頁頭
- 該部分用來記錄數(shù)據(jù)頁的狀態(tài)信息,由14個部分組成科乎,共56字節(jié):
Infimum和Supremum Record
- 在InnoDB中壁畸,每個數(shù)據(jù)頁都有兩個虛擬的行記錄,用來限定記錄的邊界喜喂。
- Infimum記錄是比該頁中任何主鍵值都要小的值瓤摧。
- Supremum指比任何可能大的值還要大的值。
- 這兩個值在頁創(chuàng)建時被建立玉吁,且在任何情況下都不會被刪除照弥。
- 示意圖如下:
User Record和Free Space
- User Record,用戶記錄进副,即行記錄这揣。
- Free Space,空閑空間影斑。是個鏈表數(shù)據(jù)結(jié)構(gòu)给赞。在一條記錄被刪除后,該空間會被加入到空閑鏈表中矫户。
Page Directory:頁目錄
- Page Directory中存放了記錄的相對位置片迅,有時將這些記錄指針稱為Slots(槽)或Directory Slots(目錄槽)。
- InnoDB中并不是每個記錄都擁有一個槽皆辽,InnoDB的槽是一個稀疏目錄柑蛇,即一個槽中可能包含多個記錄。當(dāng)記錄被插入或刪除時驱闷,需要對槽進行分裂或平衡的維護操作耻台。
- 在槽中記錄按照索引鍵信息順序存放,這樣可以利用二叉查找迅速找到記錄的指針空另。
- 需要注意的是:B+樹索引本身并不能找到具體的一條記錄盆耽,能找到只是該記錄所在的頁。數(shù)據(jù)庫把頁載入到內(nèi)存扼菠,然后通過Page Directory再進行二叉查找摄杂。只不過二叉查找的時間復(fù)雜度很低,同時在內(nèi)存中查找很快循榆,因此通常忽略這部分時間匙姜。
File Trailer:文件結(jié)尾信息
- 為了檢測頁是否已完整地寫入磁盤(如寫入時可能發(fā)生磁盤損壞、機器關(guān)機等)冯痢,InnoDB設(shè)置了File Trailer來保證頁的完整性。