簡介
從InnoDB邏輯存儲結(jié)構(gòu)來看,InnoDB所有數(shù)據(jù)都存放到在一個空間中,稱之為表空間。如圖所示,表空間由段、區(qū)、頁組成。
表空間
表空間可以看做是InnoDB存儲引擎邏輯結(jié)構(gòu)的最外層。之前的文章Mysql——InnoDB存儲引擎架構(gòu)就已經(jīng)介紹過了,表空間分為系統(tǒng)表空間、獨(dú)立表空間、常規(guī)表空間、undo獨(dú)立表空間、共享臨時表空間。
段
表空間是由各個段組成的,常見的段有數(shù)據(jù)段、索引段、回滾段等。
- 數(shù)據(jù)段:B+樹的葉子節(jié)點(diǎn);
- 索引段:B+樹的中間節(jié)點(diǎn);
- 回滾段:記錄undo log。
區(qū)
段是由各個區(qū)組成的,而區(qū)是由多個頁組成的,在任何情況下每個區(qū)的大小都為1M。為了保證區(qū)中頁的連續(xù)性,InnoDB存儲引擎申請空間是按區(qū)為單位申請(一次從磁盤申請4~5個區(qū))。默認(rèn)的情況下,InnoDB中頁的大小=16KB,即一個區(qū)有64個連續(xù)頁。
當(dāng)然Mysql 5.7中InnoDB存儲引擎時支持頁的壓縮,每個頁的大小可能是2K、4K、8K、16K,所以每個區(qū)對應(yīng)頁的數(shù)量變成64~512之間。
考慮這樣的一個問題,在用戶啟用了參數(shù)
innodb_file_per_table
后,創(chuàng)建的表默認(rèn)大小為96K,上面說到每個區(qū)的是連續(xù)的64個頁,創(chuàng)建表的時候應(yīng)該是1M才對?
其實(shí)是每個段開始的時候,會先用32個頁大小的碎片頁來存放數(shù)據(jù)(也就是說創(chuàng)建表的時候默認(rèn)會用到6個碎片頁),當(dāng)著32個碎片頁用完的時候才會申請連續(xù)的64個頁。對于一些小表來說,可以再開始的時候申請較少的空間,節(jié)省磁盤空間。
頁
頁是InnoDB磁盤管理的最小單位,默認(rèn)的情況下,InnoDB中頁的大小=16KB,數(shù)據(jù)庫一開始可以通過innodb_page_size
將頁的大小設(shè)置為4K、8K、16K,但是一旦數(shù)據(jù)庫表被創(chuàng)建了,后面就不能再修改了。
常見的頁類型有:
- 索引、數(shù)據(jù)頁(B+Tree Node);
- undo頁(undo log Page);
- 系統(tǒng)頁(System Page);
- 事務(wù)數(shù)據(jù)頁(Transaction system Page);
- 插入緩沖位圖頁(Insert Buffer Bitmap);
- 插入緩沖空閑列表頁(Insert Buffer Free List);
- 未壓縮的二進(jìn)制大對象頁(Uncompressed BLOB Page);
- 壓縮的二進(jìn)制大對象頁(Compressed BLOB Page)。
索引、數(shù)據(jù)頁結(jié)構(gòu)
即InnoDB存儲引擎B+樹的節(jié)點(diǎn)。InnoDB的數(shù)據(jù)頁由以下7個部分組成:
- File Header(文件頭):38字節(jié),用來記錄頁的頭部信息,由8個部分組成。
名稱 | 空間(字節(jié)) | 說明 |
---|---|---|
FIL_PAGE_SPACE_OR_CHKSUM | 4 | 代表該頁的checksum值 |
FIL_PAGE_OFFSET | 4 | 表空間頁的偏移值,也就是該頁在某個表空間所有頁的的位置 |
FIL_PAGE_PREV | 4 | 當(dāng)前頁的上一個頁的位置 |
FIL_PAGE_NEXT | 4 | 當(dāng)前頁的下一個頁的位置 |
FIL_PAGE_LSN | 8 | 該頁最后一次被修改的日志序列位置LSN |
FIL_PAGE_TYPE | 2 | 頁的類型 |
FIL_PAGE_FILE_FLUSH_LSN | 8 | “文件已經(jīng)被刷新到磁盤上,至少到這個lsn”,僅在文件的第一頁有效 |
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID | 4 | 頁屬于哪個表空間 |
頁的類型主要有以下幾種:
名稱 | 16進(jìn)制 | 說明 |
---|---|---|
FIL_PAGE_TYPE_ALLOCATED | 0x000 | 該頁為最新分配 |
FIL_PAGE_UNDO_LOG | 0x002 | Undo log頁 |
FIL_PAGE_INODE | 0x003 | 索引節(jié)點(diǎn) |
FIL_PAGE_IBUF_FREE_LIST | 0x004 | 插入緩沖空閑列表 |
FIL_PAGE_IBUF_BITMAP | 0x005 | 插入緩沖位圖 |
FIL_PAGE_TYPE_SYS | 0x006 | 系統(tǒng)頁 |
FIL_PAGE_TYPE_TRX_SYS | 0x007 | 事務(wù)系統(tǒng)數(shù)據(jù) |
FIL_PAGE_TYPE_FSP_HDR | 0x008 | File Space Header |
FIL_PAGE_TYPE_XDES | 0x009 | 擴(kuò)展描述頁 |
FIL_PAGE_TYPE_BLOB | 0x00A | BLOB頁 |
FIL_PAGE_INDEX | 0x45BF | B+樹葉節(jié)點(diǎn) |
- Page Header(頁頭):56字節(jié),用來記錄數(shù)據(jù)頁的狀態(tài)信息,由14個部分組成。
名稱 | 空間(字節(jié)) | 說明 |
---|---|---|
PAGE_N_DIR_SLOTS | 2 | 表示頁目錄的插槽數(shù) |
PAGE_HEAP_TOP | 2 | 堆中第一個記錄的指針,記錄在頁中是根據(jù)堆的形式存放的 |
PAGE_N_HEAP | 2 | 堆中的記錄數(shù),第15位表示行記錄格式 |
PAGE_FREE | 2 | 指向可重用空間的首指針 |
PAGE_GARBAGE | 2 | 已刪除記錄的字節(jié)數(shù) |
PAGE_LAST_INSERT | 2 | 最后插入記錄的位置 |
PAGE_DIRECTION | 2 | 最后插入的方向: PAGE_LEFT(0x01) PAGE_RIGHT(0x02) PAGE_SMAE_REC(0x03) PAGE_SMAE_PAGE(0x04) PAGE_NO_DIRECTION(0x05) |
PAGE_N_DIRECTION | 2 | 一個方向連續(xù)插入的記錄數(shù) |
PAGE_N_RECS | 2 | 該頁的記錄數(shù) |
PAGE_MAX_TRX_ID | 8 | 修改當(dāng)前頁最大的事務(wù)ID,注意該值僅在二級索引定義 |
PAGE_LEVEL | 2 | 當(dāng)前頁在索引樹的層數(shù) |
PAGE_INDEX_ID | 8 | 索引ID,代表該頁屬于哪個索引 |
PAGE_BTR_SEG_LEAF | 10 | B+樹數(shù)據(jù)頁非葉子節(jié)點(diǎn)所在段的segment header |
PAGE_BTR_SEG_TOP | 10 | B+樹數(shù)據(jù)頁所在段的segment header |
- Infimun和Supremum Records:屬于InnoDB存儲引擎每個頁中的兩個虛擬行記錄,用來限定記錄的邊界。Infimun:下界,表示比任何主鍵值還要小的值;Supremum:上界,表示比任何主鍵值還要大的值;這兩個值在頁創(chuàng)建的時候被建立,在任何情況下都不會被刪除。
- User Records(行記錄):用戶實(shí)際存儲的行記錄。
- Free Space(空閑空間):頁的空閑空間。
- Page Directory(頁目錄):存放記錄的相對位置的指針,這些指針可以稱之為Slots(槽)。在InnoDB中并不是每個記錄都擁有一個Slot,而是一個稀疏目錄,也就是說一個Slot可能包含多個記錄。在Slots中記錄是按照索引鍵值順序存放的,這樣可以利用二叉查找樹迅速查找到對應(yīng)的Slot。用戶查找數(shù)據(jù)的時候先通過B+樹找到對應(yīng)的頁,然后將頁加載到內(nèi)存,然后通過Page Directory進(jìn)行二叉查找,找到對應(yīng)的Slot,然后通過該Slot指向的記錄進(jìn)行next_record繼續(xù)查找相對應(yīng)的記錄。
- File Trailer(文件結(jié)尾信息):8字節(jié),為了檢測頁是否已經(jīng)完整寫入磁盤(如可能發(fā)生寫入過程中磁盤損壞,機(jī)器關(guān)機(jī)等)。
名稱 | 空間(字節(jié)) | 說明 |
---|---|---|
FILE_PAGE_END_LSN | 8 | 前4字節(jié)表示頁的checksum值;后4字節(jié)與File Header的FIL_PAGE_LSN相同。將這兩個值與File Header的FIL_PAGE_SPACE_OR_CHKSUM和FIL_PAGE_LSN進(jìn)行比較,確認(rèn)是否一致,以此來保證頁的完整性。 |
行
在InnoDB存儲引擎中,表都是根據(jù)主鍵順序組織存放的,即按照主鍵作為索引鍵值,因此InnoDB存儲引擎中的表實(shí)際上就是顆B+Tree,樹的節(jié)點(diǎn)存放的是索引、數(shù)據(jù)頁,其中葉子節(jié)點(diǎn)存放的是數(shù)據(jù),非葉子節(jié)點(diǎn)存放的是索引,每個葉子節(jié)點(diǎn)包含多行數(shù)據(jù)。
InnoDB存儲引擎對每個頁數(shù)據(jù)的存放是有硬性要求的,最多允許16KB/2 - 200 = 7992
行數(shù)據(jù),最少需要2行數(shù)據(jù)。
行記錄格式
InnoDB存儲引擎提供了Redundant、Compact(Mysql5.1 默認(rèn))、Dynamic(Mysql5.7 默認(rèn))、Compressed四種格式來存放行記錄,可以使用show table status;
查看表中的行記錄格式。
Compact行記錄格式
上圖展示了Compact行記錄格式,主要有以下幾部分:
變長字段列表:存儲變長字段的長度,按照列的順序逆序來存儲;如果列的長度<255,用1個字節(jié)表示;否則使用2個字節(jié)表示(注意,VARCHAR最大長度為65535);
NULL標(biāo)志位:表示改行數(shù)據(jù)是否有空值,有則用1表示,該部分占用1個字節(jié);
-
記錄頭信息:固定5個字節(jié)。存儲格式如下:
名稱 空間(bit) 說明 () 1 未知 () 1 未知 delete_flag 1 該行是否被刪除 min_rec_flag 1 如果等于1,該記錄是預(yù)先被定義為最小的記錄 n_owned 4 該記錄擁有的記錄數(shù) heap_no 13 索引堆中該條記錄的排序記錄 record_type 3 記錄類型:
000表示普通
001表示B+樹節(jié)點(diǎn)指針
010表示Infimum
011表示Supremun
1XX表示保留next_record 16 頁中下一條記錄的相對位置 實(shí)際存儲每個列的數(shù)據(jù);
需要注意的是如果某個列的值為NULL,則不占任何空間(除了占有NULL標(biāo)志位,為1)。
每行數(shù)據(jù)除了用戶定義的以外,還有兩個隱藏列:事務(wù)ID列(6個字節(jié))和回滾指針列(7個字節(jié))。
如果表沒有定義主鍵,每行還會增加一個rowid列。
Redundant行記錄格式
Redundant是Mysql5.0版本之前的行記錄格式。上圖展示了Redundant行記錄格式,主要有以下幾部分:
字段長度偏移列表:存儲所有字段長度的偏移量,按照列的順序逆序來存儲(例如:23 20 16 14 13 0c 06字段長度偏移列表,06代表第一列的長度=6,0c-06=第二列的長度,13-0c=第三列的長度,以此類推);如果列的長度<255,用1個字節(jié)表示;否則使用2個字節(jié)表示(注意,VARCHAR最大長度為65535);
-
記錄頭信息:占用6個字節(jié),存儲格式如下:
名稱 空間(bit) 說明 () 1 未知 () 1 未知 delete_flag 1 該行是否被刪除 min_rec_flag 1 如果等于1,該記錄是預(yù)先被定義為最小的記錄 n_owned 4 該記錄擁有的記錄數(shù) heap_no 13 索引堆中該條記錄的排序記錄 n_fileds 10 記錄中列的數(shù)量 1byte_offs_flag 1 偏移列表為1個字節(jié)還是2個字節(jié) next_record 16 頁中下一條記錄的相對位置 實(shí)際存儲每個列的數(shù)據(jù);
①考慮這樣的一個問題,之前說到頁的大小默認(rèn)16K(16384個字節(jié)),并且一個數(shù)據(jù)頁中至少要包含2行記錄,然而Mysql Varchar最大存儲字節(jié)65535個字節(jié)(當(dāng)然還有其他可變長大字段,TEXT、BLOB) > 頁的大小,兩者之間是否會互相矛盾?
事實(shí)上會互相矛盾,但mysql引入行溢出的機(jī)制來解決這個問題。
Compact和Redundant行記錄格式采用上圖存儲方式,數(shù)據(jù)頁只保留前768個字節(jié)的前綴數(shù)據(jù),之后是偏移量指針,指向行溢出頁。
②什么時候會觸發(fā)行溢出呢?
剛剛講到一個數(shù)據(jù)頁至少要包含2行記錄,因此,如果當(dāng)前頁只能存放一個記錄的時候,InnoDB存儲引擎就會自動把行數(shù)據(jù)存放到溢出頁中。
③對于TEXT或者BLOB,是不是總是存放到溢出頁中?
是否存放到溢出頁中,都要遵循上面第②點(diǎn)。
④Varchar最大存儲字節(jié)65535個字節(jié),是指單個列的長度么?
不是的,是指一行記錄的所有VARCHAR列長度的總和不能超過65535個字節(jié)。
⑤Varchar(n)或者Char(n)中的n指的是字節(jié)個數(shù)還是字符長度?
指的是字符長度。不同的字符集對列的長度會有影響,比如定義Varchar的最大長度,lantin則可以定義Varchar(65535),如果是utf-8(3個字節(jié))則只能定義Varchar(65535/3)。對于char(n)來說,不同的字符集長度是不固定的,InnoDB存儲引擎在內(nèi)部會將其視為變長字符類型。
Dynamic行記錄格式
InnoDB 1.0X版本開始引入新的文件格式,以前支持Compact和Redundant格式稱為Antelope文件格式,新的格式有:Compressed和Dynamic,稱為Baracuda文件格式。
Dynamic行格式提供與Compact行格式相同的存儲特性,但對于長可變長度列值(對于VARCHAR,VARBINARY和BLOB和TEXT類型)行溢出采用了完全的行溢出方式。如圖所示,在數(shù)據(jù)頁的某行記錄里的某個大可變長度列值只存放20個字節(jié)的指針,實(shí)際的數(shù)據(jù)都存放到外部溢出頁中。Compressed行記錄格式
Compressed行格式提供與Dynamic行格式相同的存儲特性和功能,但增加了對表和索引數(shù)據(jù)壓縮的支持(采用zlib算法壓縮)。