索引組織表(index organized table)
在InnoDB存儲引擎中,表都是根據主鍵順序組織存放的,這種存儲方式的表叫索引組織表。在InnoDB存在引擎表中,每張表都有個主鍵(Primary key),如果在創建表時沒有顯示定義主鍵,則會按照如下方式選擇或者創建主鍵:
(1) 判定是否有非空的唯一索引(unique not null),如果有則該列即為主鍵。若果有多個,則選擇建表是第一個定義的非空位于索引為主鍵。注意:主鍵的選擇根據的是定義索引的順序,而不是建表時的列的順序。
(2) 如果不存在唯一索引,InnoDB存儲引擎字段創建一個6字節大小的指針(僅內部可見)。
InnoDB邏輯存儲結構
在InnoDB存儲引擎中,所有的數據都被邏輯地存放在一個空間中,稱之為表空間(tablespace)。表空間又由段(segment)、區(extent)、頁(page)組成。InnoDB存儲引擎的邏輯存儲結構如下圖。
表空間 可以看見InnoDB存儲引擎邏輯結構的最高層,所有的數據都存放在表空間中。表空間又分為獨立表空間和共享表空間。通過參數innodb_file_per_table參數來決定使用何種類型的表空間。但是需要注意的是獨立表空間內只存放數據、索引和插入緩沖頁,其他的數據,如回滾(undo)信息、插入緩沖索引頁、系統事務信息、二次寫緩沖(double write buffer)等還是放置在原來的共享表空間中。
段 表空間由各個段組成。常見的段有數據段、索引段、回滾段等。InnoDB存儲引擎是索引組織表,因此數據即索引,索引即數據。數據段即為B+樹的葉子點(leaf node segment),索引段為B+數據的非索引節點(non-leaf node segment)。回滾段比較特殊以后在介紹。段都是引擎自身管理的。
區 區是由連續頁組成的空間。InnoDB存儲引擎頁的大小為16KB,一個區有64個連續的頁組成,所以每個區的大小都是1MB。參數innodb_page_size可設置頁的大小4K、8K,但是,不論頁的大小怎么變化,區的大小不變1M。但是有這樣一個問題:在開啟獨立表空間之后,創建的表默認大小是96K,區中是64個連續的頁,創建的表空間應該是1M才對呀?這是因為在每個段的開始時,先用32個頁大小的碎片頁(fragment page)來保存數據,在使用完這些頁之后才是64個連續的頁的申請。這樣做是對于一些小表或者undo這類的段,可以在開始時申請較少的空間,節省磁盤容量的開銷。
頁 頁是InnoDB磁盤管理的最小單位。默認大小為16K,可以通過innodb_page_size將頁的大小設置為4K、8K、16K,則所有表中頁的大小都為設置值,不可以對其再次修改。除非通過mysqldump導入和導出操作來產生新的庫。常見的頁的類型有:數據頁(B-tree Node)、undo頁(unod Log Page)、系統頁(System Page)、事務數據頁(Transaction system Page)、插入緩沖空閑列表頁(Insert Buffer Free List)、未壓縮的二進制大對象頁(Uncompressed BLOB Page)、壓縮的二進制對象頁(compressed BLOB Page)。
行 InnoDB存儲引擎是面向行的(row-oriented),也就是說數據是按行進行存放的。每個頁存放的行記錄也是有硬性定義的,最多運行存放(16K/2-200)行的記錄,即7992行記錄。
InnoDB物理存儲結構
InnoDB表由共享表空間(ibdata1),redo日志文件組(ib_logfile0,ib_logfile1),表結構定義文件(表名.frm)組成。當開啟獨立表空間時,還有以表名.ibd的文件,存儲數據,索引,插入緩存列。
InnoDB行記錄格式
InnoDB存儲引擎的記錄是以行的形式存儲的,這就表明頁中保存著表中一行行的數據。其類型有REDUNDANT、 COMPACT、COMPRESS、DYNAMIC四種。可以通過show table status。
COMPACT 在MySQL 5.0中引入,其設計目標是高效的存儲數據。也就是一個頁中存放的行數據越多,其性能越高。compact行記錄的存放方式:
第一部分是一個非NULL變長字段長度列表(字節數與非NULL變長字段數相同),且其是按列的順序逆序放置的。
第二部分是NULL標志位(1個bit表示對應列的NULL),該位指示了改行數據中是否有NULL值。
-
第三部分是記錄頭信息,固定占用5字節(40位),每位含義如下:
最后的部分就是實際存儲每列的數據。
- 需要注意的是:
- NULL除了占有NULL標志位,實際存儲不占任何空間。
- 每行數據除了用戶定義的列之外,還有兩個隱藏列,事務ID列和回滾指針列。分別為6字節和7字節的大小。若InnoDB表沒有定義主鍵,每行還會增加一個6字節的rowid列。
- 固定長度CHAR字段在未能完全占用其長度空間時,會用0x20來進行填充。
- 記錄頭信息的最后兩個字節代表next_recorder,代表下一條記錄的偏移量,所以InnoDB在頁內部是通過一種鏈表的結構來串連各個行記錄的。
REDUNDANT
- Redundant是MySQL5.0版本之前InnoDB的行記錄格式,其存在是為了兼容老版本的頁格式。
- Redundant行記錄存儲方式:
第一部分是一個字段長度偏移列表,同樣是按列的順序逆序放置的。
-
第二部分是記錄頭信息,不同于Compact,Redundant占用6字節(48位),每位含義如下:
- 其中n_fields值代表一行中列的數量,占用10位。這也解釋了為什么MySQL 一行支持的最多列數為1023。
- 需要注意的是:
- 對于NULL值的處理,Redundant和Compact非常不同:對于VARCHAR類型的NULL值,Redundant同樣不占用任何空間,但CHAR類型的NULL值需要占用最大值字節數大小的空間。
行溢出數據
InnoDB可以將一條記錄中的某些數據存儲在真正的數據頁面之外。
是否溢出與列類型是否為BLOB等大對象列類型并無直接關系。而是根據“保證一個頁至少能存放兩條記錄”的標準來判斷的。如果VERCHAR類型的列長度過長導致一頁只能存儲一條記錄,則也會被放到Uncompressed BLOB Page(行溢出數據頁)。之所以有這個標準的原因,是因為如果不能保證如此,那B+Tree就是去意義變成鏈表了。
InnoDB能存放VARCHAR類型的最大長度為65532字節 (注意并非65535,這其中還有其他開銷)。另外要注意,VARCHAR(N)中的N指的是字符的長度而非字節。另外,MySQL手冊中定義的65535字節長度是指所有VARCHAR列的長度總和。
當發生行溢出時,數據頁中值保存了列的前768字節的前綴數據,之后是偏移量,指向行溢出頁。如下圖所示:
Compressed和Dynamic行記錄格式
- 從InnoDB1.0.x開始引入了新的文件格式(可理解為頁格式):Barracuda。而之前的文件格式被稱為Antelope,Barracuda包含了Antelope:
- 新的兩種行記錄格式對于存放在BLOB中的數據采用了完全的行溢出方式,在數據頁中只存放20個字節的指針,實際的數據都存放子頁OffPage中。而之前的兩種行記錄格式都是會存放768個前綴字節。新的行溢出方式如下:
- Compressed行記錄格式的另一個功能就是:存儲在其中的行數據會以zlib的算法進行壓縮。因此對于BLOB、TEXT、VARXCHAR這些大長度類型的數據能夠非常有效的存儲。
CHAR的行存儲結構
- 從MySQL4.1開始,CHAR(N)中的N指的是字符的長度,而不是之前版本的字節長度。也就是說在不同字符集下,CHAR類型列內部存儲的可能不是定長的數據。
- 另外由于多字節的字符編碼,不同字符的長度可能不同,所以CHAR類型不再代表固定長度的字符串了。因此,對于多字節字符編碼的CHAR類型的存儲,InnoDB在內部將其視為變長字符類型。這也就意味著在變長長度列表中會記錄CHAR數據類型的長度。只是對于未能占滿長度的字符還是填充0x20。
InnoDB數據頁結構
- 通過前面內容我們已了解到,頁是InnoDB管理數據庫的最小磁盤單位。
- InnoDB數據頁由以下七部分組成:
File Header:文件頭
- File Header用來記錄頁的一些頭信息,共由如下8部分組成,共占用38字節:
- InnoDB頁的類型:
Page Header:頁頭
- 該部分用來記錄數據頁的狀態信息,由14個部分組成,共56字節:
Infimum和Supremum Record
- 在InnoDB中,每個數據頁都有兩個虛擬的行記錄,用來限定記錄的邊界。
- Infimum記錄是比該頁中任何主鍵值都要小的值。
- Supremum指比任何可能大的值還要大的值。
- 這兩個值在頁創建時被建立,且在任何情況下都不會被刪除。
- 示意圖如下:
User Record和Free Space
- User Record,用戶記錄,即行記錄。
- Free Space,空閑空間。是個鏈表數據結構。在一條記錄被刪除后,該空間會被加入到空閑鏈表中。
Page Directory:頁目錄
- Page Directory中存放了記錄的相對位置,有時將這些記錄指針稱為Slots(槽)或Directory Slots(目錄槽)。
- InnoDB中并不是每個記錄都擁有一個槽,InnoDB的槽是一個稀疏目錄,即一個槽中可能包含多個記錄。當記錄被插入或刪除時,需要對槽進行分裂或平衡的維護操作。
- 在槽中記錄按照索引鍵信息順序存放,這樣可以利用二叉查找迅速找到記錄的指針。
- 需要注意的是:B+樹索引本身并不能找到具體的一條記錄,能找到只是該記錄所在的頁。數據庫把頁載入到內存,然后通過Page Directory再進行二叉查找。只不過二叉查找的時間復雜度很低,同時在內存中查找很快,因此通常忽略這部分時間。
File Trailer:文件結尾信息
- 為了檢測頁是否已完整地寫入磁盤(如寫入時可能發生磁盤損壞、機器關機等),InnoDB設置了File Trailer來保證頁的完整性。