自己分析一下ibd文件還是蠻有意思的救赐,能夠?qū)W到不少東西,建議跟著走一遍当犯,慢慢領(lǐng)會作者設(shè)計的意圖
人學(xué)東西總是先感性的認識,慢慢到理性 —— 過程中大腦需要理解和消化
mysql版本5.7.26
?
先貼一張數(shù)據(jù)頁的結(jié)構(gòu)圖-方便對整體有個印象
利用工具查看數(shù)據(jù)庫文件ibd的頁分布情況 (工具名py_innodb_page_info)
當你熟悉page的格式后杈曲,自己也能寫一個這樣的工具
test.ibd文件頁分布情況
- page offset 00000003, page type <B-tree Node>, page level <0001>
- page offset 00000004, page type <B-tree Node>, page level <0000>
- page offset 00000005, page type <B-tree Node>, page level <0000>
- page offset 00000006, page type <B-tree Node>, page level <0000>
以上四個都是數(shù)據(jù)頁
我們分析 page offset 00000006, page type <B-tree Node>, page level <0000>
page level <0000> 表示的是葉子節(jié)點
使用十六進制工具Synalyze It! 打開/usr/local/mysql/data/nishui/test.ibd 文件(文件權(quán)限問題此處忽略,二進制工具任意)
找到 00000006 頁在文件中的位置
因為看的是00000006數(shù)據(jù)頁驰凛, 用6 * 16 * 1024 = 98304B (innodb引擎默認一頁16KB(可通過innodb_page_size改變頁大小)担扑,然而1KB= 1024B) 轉(zhuǎn)換成十六進制為 0x18000
show global status like 'Innodb_page_size' 可查看當前頁大小
找到該位置截圖如下:
這個頁便是 00000006 數(shù)據(jù)頁開始的位置了, 可以開始分析詳細數(shù)據(jù)了
一恰响、先分析File Header(38字節(jié)-描述頁信息)
- 72 08 C8 7F -> 數(shù)據(jù)頁的checksum值
- 00 00 00 06 -> 頁號(偏移量)
- 00 00 00 05 -> 前一頁是第5頁
- FF FF FF FF -> 由于沒有下一頁,因此為該值
- 00 00 00 00 00 38 23 77 -> 頁的LSN
- 45 BF -> 頁的類型 0x45BF代表數(shù)據(jù)頁涌献,剛好這頁是數(shù)據(jù)頁
- 00 00 00 00 00 00 00 00 -> 獨立表空間胚宦,該值為0
- 00 00 00 5B -> 表空間的SPACE ID
二、分析Page Header(56字節(jié)-記錄頁的狀態(tài)信息)
SHOW TABLE STATUS LIKE 'test'查看表行記錄格式
- 00 07 -> 代表Page Directory 有7個槽
-
17 27 -> 代表空閑空間開始位置的偏移量燕垃,即 0x18000 + 0x1727 = 0x19727,觀察這個位置枢劝,這是最后一行的結(jié)束,后面都是空閑的
06頁空閑邏輯位置 - 80 1D -> 當前為 Compact 格式卜壕,第15位表示行記錄格式您旁,再加上兩條偽記錄, 因此0x801D - 0x8002 = 0x001B轴捎,代表該頁中實際的記錄有27條記錄
- 00 00 -> 指向頁中空閑位置(偏移量)
- 00 00 -> PAGE_GARBAGE 表示沒有刪除的數(shù)據(jù)
-
16 4A -> PAGE_LAST_INSERT 最后插入記錄的位置偏移 0x18000 + 0x164A = 0x1964A 直接指向最后一行數(shù)據(jù)存儲的地址鹤盒,也就是id為199,這條確實是最后一條插入的
test表中最后一條記錄圖 - 00 02 -> PAGE_DIRECTION 最后插入的方向,向右邊插入
- 00 1A -> PAGE_N_DIRECTION 一個方向連續(xù)插入記錄的數(shù)量 連續(xù)插入26個
- 00 1B -> PAGE_N_RECS 當前數(shù)據(jù)頁中含有27條記錄
- 00 00 00 00 00 00 00 00 修改當前頁的最大事務(wù)ID
- 00 00 -> 代表頁為葉子節(jié)點
- 00 00 00 00 00 00 00 43 -> 索引ID侦副,表示當前頁屬于哪個索引
- 00 00 00 42 00 00 00 02 00 F2 -> B+樹數(shù)據(jù)頁非葉子節(jié)點所在段的segment header侦锯。注意該值僅在樹的root頁中定義
- 00 00 00 42 00 00 00 02 00 32 ->B+樹數(shù)據(jù)頁所在段的segment header。
小結(jié)一下
1.innodb在整個頁可以使用的空間當成heap,當需要插入記錄的時候秦驯,首先會檢查PAGE_FREE指向的空閑空間尺碰,若申請的空間小于等于該空間容量時,那么使用該空閑空間译隘,否者從PAGE_HEAP_TOP指向的空閑空間進行分配
heap中存儲的記錄非物理連續(xù)的亲桥,只是邏輯上連續(xù)的,可用下圖表示
2.PAGE_LAST_INSERT细燎、PAGE_DIRECTION两曼、PAGE_N_DIRECTION主要使用來做頁分裂操作的
數(shù)據(jù)頁中行記錄存儲的排列方式
三、偽記錄分析Infimum + Supremum Record (26字節(jié)-兩個虛擬行記錄)
innodb存儲引擎有兩個偽記錄玻驻,用來界定行記錄的邊界
數(shù)據(jù)從 0x1805E 到 0x18077
- 01 00 02 00 1E -> recorder header (5字節(jié))
- 69 6E 66 69 6D 75 6D 00 -> 只有一個列的偽記錄悼凑,記錄內(nèi)容就是Infimum(多了一個 0x00 字節(jié)) (8字節(jié))
- 08 00 0B 00 00 -> recorder header (5字節(jié))
- 73 75 70 72 65 6D 75 6D -> 只有一個列的偽記錄偿枕,記錄內(nèi)容就是Supremum (8字節(jié))
分析下偽記錄中的recorder header中的next_record
recorder header最后兩個字節(jié) 0x001E,表示下一個記錄位置的偏移量,即當前行記錄“內(nèi)容”的位置0x18063 + 0x001E户辫,即0x18081渐夸,這個位置就是存放第一條實際用戶記錄
四、分析User Record
當前 Row Format 為Compact格式 可通過命令show table status like 'table_name' 進行查看
CREATE TABLE `test` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`t1` varchar(10) DEFAULT NULL,
`t2` varchar(15) DEFAULT NULL,
`t3` int(11) DEFAULT NULL,
`t4` varchar(1500) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=200 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
創(chuàng)建數(shù)據(jù)的腳本
CREATE DEFINER=`root`@`localhost` PROCEDURE `insert_test`( )
BEGIN
#Routine body goes here...
declare i int;
declare tmp int;
set i=1;
set tmp = 1;
while i<200 do
if tmp=1 then
insert into test (t1, t4) values('a', REPEAT('a', i));
set tmp = 0;
else
insert into test (t1, t3, t4) values('a', i, REPEAT('a', i));
set tmp = 1;
end if;
set i=i+1;
end while;
END
由上面?zhèn)斡涗汭nfimum的Record Header可知下一條記錄的開始地址是0x18081,順便把前面的extra info也分析下,截圖如下
- AD 80 01 -> 變長字段長度列表渔欢, 逆序,第一列是1字節(jié)墓塌,第四列是2字節(jié),所以第一列包含1個字符奥额,第四列包含173個 字符80 AD存的是補碼苫幢,換算成原碼為0x00AD,轉(zhuǎn)換成10進制就是173
-
02 -> 二進制(00000010)表示第二個字段為null
- 00 00 10 00 CC -> Record Header 固定5字節(jié)長度
- 00 00 00 AD -> 由于是自動創(chuàng)建的int自增id 垫挨,固是4個字節(jié)韩肝,當前行記錄id為173, 由于該id是無符號的,所以最高位不是符號位
- 00 00 00 00 69 E7 -> TransactionId
- D7 00 00 01 5C 01 10 -> Roll Pointer
- 61 -> 第一列字段數(shù)據(jù) a
- 80 00 00 AD -> 第二列,存的是補碼九榔,因此原碼為0xAD哀峻,故值為173
- 第三列為null,不占用空間
- 61 .... 61 -> 第四列字段數(shù)據(jù) a ... a 173個 省略
分析User Record中Record Header中的內(nèi)容
0x 00 00 10 00 CC 轉(zhuǎn)換成十進制如下 00000000 00000000 00010000 00000000 11001100
下面都是二進制的哲泊,其他的都是十六進制的
- 0 -> 預(yù)留位1
- 0 -> 預(yù)留位2
- 0 -> delete_mask 標記該記錄是否刪除剩蟀,0表示沒有刪除 說明刪除的數(shù)據(jù)很可能還在頁中,并且占用著空間
- 0 -> min_rec_mask 標記該記錄是否為B+樹的非葉子節(jié)點中的最小記錄
- 0000 -> n_owned 表示當前槽管理的記錄數(shù)
- 00000000 00010 -> heap_no 表示當前記錄在記錄堆的位置信息切威,這個值表示當前記錄在heap中的位置為2
- 000 -> record_type 表示當前記錄的類型育特,0表示普通記錄
-
00000000 11001100 -> next_record 表示下一條記錄的相對位置,轉(zhuǎn)換16進制為0xCC,0x18081 + 0xCC = 0x1814D牢屋,下一條記錄的值地址為0x1814D且预,截圖如下
簡單用圖可表示如下(忽略實際內(nèi)容):
五槽袄、分析Page Directory
這一頁的末尾是0x1BFFF烙无,并且加上Page Header中PAGE_N_DIR_SLOTS,能夠知道Page Directory中包含了7個slot 截圖如下
位置是從 0x1BFEA - 0x1BFF7,一共14個字節(jié),因此展開如下:
- 00 70 -> supremum記錄所在行偏移量地址
- 10 2C -> id為192的行偏移量地址
- 0C C2 -> id為188的行偏移量地址
- 09 68 -> id為184的行偏移量地址
- 06 1E -> id為180的行偏移量地址
- 02 E4 -> id為176的行偏移量地址
- 00 63 -> infimum記錄所在行偏移量地址
六遍尺、分析File Tailer
固定占用8個字節(jié)截酷,并且是在頁尾部,可以直接得出位置為0x1BFF8 開始的
- 72 08 C8 7F -> Old-style Checksum
- 00 38 23 77 -> Low 32 bit of LSN
為了保證頁能夠完整地寫入磁盤(如可能發(fā)生的寫入過程中磁盤損壞乾戏、機器宕機等原因)迂苛,InnoDB存儲引擎的頁中設(shè)置了File Trailer部分。File Trailer只有一個FIL_PAGE_END_LSN部分鼓择,占用8個字節(jié)三幻。前4個字節(jié)代表該頁的checksum值,最后4個字節(jié)和File Header中的FIL_PAGE_LSN相同呐能。通過這兩個值來和File Header中的FIL_PAGE_SPACE_OR_CHKSUM和FIL_PAGE_LSN值進行比較念搬,看是否一致(checksum的比較需要通過InnoDB的checksum函數(shù)來進行比較抑堡,不是簡單的等值比較),以此來保證頁的完整性(not corrupted)朗徊。
數(shù)據(jù)頁格式
File Header
名稱 | 大惺籽(字節(jié)) | 說明 |
---|---|---|
FIL_PAGE_SPACE_OR_CHKSUM | 4 | 當mysql為4.0.14之前的版本時,該值為0爷恳。在之后的mysql版本中有缆,該值代表頁的checksum值(一種新的checksum值) |
Fil_PAGE_OFFSET | 4 | 表空間中頁的偏移值,如果獨立表空間a.ibd的大小為1GB温亲,如果頁的大小為16kb棚壁,那么總共有65536個頁.FIL_PAGE_OFFSET表示該頁在所有頁中的位置。若此表空間的ID為10栈虚,那么搜索頁(10, 1)就表示在表a中的第二頁 |
FIL_PAGE_PREV | 4 | 當前頁的上一頁灌曙,B+樹的特性就決定了葉子節(jié)點必須是雙向列表 |
FIL_PAGE_NEXT | 4 | 當前頁的下一頁 |
FIL_PAGE_LSN | 8 | 該值代表頁最后被修改的日志序列位置LSN |
FIL_PAGE_TYPE | 2 | INNODB 存儲頁的類型, |
FIL_PAGE_FILE_FLUSH_LSN | 8 | 該值僅在系統(tǒng)表空間的一個頁中定義节芥,代表文件至少更新到了LSN值在刺。對于獨立表空間,該值為0 |
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID | 4 | 從mysql4.1開始头镊,該值代表屬于哪個表空間 |
Innodb存儲引用中頁的類型
名稱 | 十六進制 | 解釋 |
---|---|---|
FIL_PAGE_INDEX | 0x45BF | B+樹葉節(jié)點 |
FIL_PAGE_UNDO_LOG | 0x0002 | undo log頁 |
FIL_PAGE_INODE | 0x0003 | 索引節(jié)點 |
FIL_PAGE_IBUF_FREE_LIST | 0x0004 | insert buffer空閑列表 |
FIL_PAGE_TYPE_ALLOCATED | 0x0000 | 該頁為最新分配頁 |
FIL_PAGE_IBUF_BITMAP | 0x0005 | insert buffer 位圖 |
FIL_PAGE_TYPE_SYS | 0x0006 | 系統(tǒng)頁 |
FIL_PAGE_TYPE_TRX_SYS | 0x0007 | 事務(wù)系統(tǒng)數(shù)據(jù) |
FIL_PAGE_TYPE_FSP_HDR | 0x0008 | File space Header |
FIL_PAGE_TYPE_XDES | 0x0009 | 擴展描述頁 |
FIL_PAGE_TYPE_BLOB | 0x000A | BLOB頁 |
Page Header
用來數(shù)據(jù)頁的狀態(tài)信息,14部分組成蚣驼,共56字節(jié)
名稱 | 大小(字節(jié)) | 說明 |
---|---|---|
PAGE_N_DIR_SLOTS | 2 | 在Page Directory (頁目錄〉中 的Slot (槽〉 數(shù)相艇,“4.4.S Page Directory” 小節(jié)中會介紹 |
PAGE HEAP TOP | 2 | 堆中第一個記錄的指針颖杏, 記錄在頁中是根據(jù)堆 的形式存放的 堆中空閑空間的位置(偏移量) |
PAGE N HEAP | 2 | 堆中的記錄數(shù). 一共占用2 字節(jié), 但是第15 位表示行記錄格式 (包括最小和最大記錄以及標記為刪除的記錄) |
PAGE FREE | 2 | 指向可重用空間的首指針 指向頁中空閑空間的位置(偏移量)(就是標記為刪除的記錄地址) |
PAGE GARBAGE | 2 | 己刪除記錄的字節(jié) 數(shù)坛芽, 即行記錄結(jié)構(gòu)中也陽也在為1的記錄大小的總數(shù) |
PAGE LAST INSERT | 2 | 最后捕入記錄的位置(偏移量) |
PAGE DIRECTION | 2 | 最后插入的方向. 可能的取值為2 留储。 1.PAGE LEFT (0x01) 2.PAGE RIGHT (Ox02) 3.PAGE SAME REC (Ox03) 4. PAGE SAME PAGE (Ox04) |
PAGE N DIRECTION | 2 | 一個方向連續(xù)插入記錄的數(shù)量 |
PAGE N RECS | 2 | 該頁中記錄的數(shù)量 |
AGE MAX TRX ID | 8 | 修改當前頁 的最大事務(wù)ID, 注意該值僅在Secondary Index中定義 |
PAGE LEVEL | 2 | 當前頁 在索引樹中的位置咙轩, OxOO代表葉節(jié)點获讳, l!P時節(jié) J點總是在第0層 |
PAGE INDEX ID | 8 | 索引ID, 表示當前頁屬于哪個索引 |
PAGE BTR SEG LEAF | 10 | B+樹數(shù)據(jù)頁非頁節(jié)點所在段的segment header活喊。 注意該值僅在 B+ 樹的 Root 頁中定義 |
PAGE BTR SEG TOP | 10 | B+樹數(shù)據(jù)頁所在段的 segment header. 注意該值僅在 B+樹的 Root 頁中定義 |
COMPACT行記錄格式
名稱 | 大胸はァ(bit) | 描述 |
---|---|---|
預(yù)留位1 | 1 | 沒有使用 |
預(yù)留位2 | 1 | 沒有使用 |
delete_mask | 1 | 標記該記錄是否被刪除 |
min_rec_mask | 1 | 標記該記錄是否為B+樹的非葉子節(jié)點中的最小記錄 |
n_owned | 4 | 表示當前槽管理的記錄數(shù) |
heap_no | 13 | 表示當前記錄在記錄堆的位置信息 |
record_type | 3 | 表示當前記錄的類型,0表示普通記錄钾菊,1表示B+樹非葉節(jié)點記錄帅矗,2表示最小記錄,3表示最大記錄 |
next_record | 16 | 表示下一條記錄的相對位置 |
Total | 40(Byte) | nothing |
參考借鑒:
InnoDB數(shù)據(jù)頁結(jié)構(gòu)
MYSQL內(nèi)核:INNODB存儲引擎 卷1-4
MySQL技術(shù)內(nèi)幕InnoDB存儲引擎第2版