InnoDB數據頁結構分析

自己分析一下ibd文件還是蠻有意思的,能夠學到不少東西,建議跟著走一遍,慢慢領會作者設計的意圖
人學東西總是先感性的認識,慢慢到理性 —— 過程中大腦需要理解和消化

mysql版本5.7.26

?

先貼一張數據頁的結構圖-方便對整體有個印象

InnoDB頁結構示意圖
InnoDB頁各組成部分簡單描述

利用工具查看數據庫文件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>
    以上四個都是數據頁
    我們分析 page offset 00000006, page type <B-tree Node>, page level <0000>
    page level <0000> 表示的是葉子節點

使用十六進制工具Synalyze It! 打開/usr/local/mysql/data/nishui/test.ibd 文件(文件權限問題此處忽略,二進制工具任意)

找到 00000006 頁在文件中的位置

因為看的是00000006數據頁, 用6 * 16 * 1024 = 98304B (innodb引擎默認一頁16KB(可通過innodb_page_size改變頁大小),然而1KB= 1024B) 轉換成十六進制為 0x18000
show global status like 'Innodb_page_size' 可查看當前頁大小

找到該位置截圖如下:


06頁開始邏輯位置

這個頁便是 00000006 數據頁開始的位置了, 可以開始分析詳細數據了

一、先分析File Header(38字節-描述頁信息)

  • 72 08 C8 7F -> 數據頁的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代表數據頁,剛好這頁是數據頁
  • 00 00 00 00 00 00 00 00 -> 獨立表空間,該值為0
  • 00 00 00 5B -> 表空間的SPACE ID

二、分析Page Header(56字節-記錄頁的狀態信息)

SHOW TABLE STATUS LIKE 'test'查看表行記錄格式

  • 00 07 -> 代表Page Directory 有7個槽
  • 17 27 -> 代表空閑空間開始位置的偏移量,即 0x18000 + 0x1727 = 0x19727,觀察這個位置,這是最后一行的結束,后面都是空閑的


    06頁空閑邏輯位置
  • 80 1D -> 當前為 Compact 格式,第15位表示行記錄格式,再加上兩條偽記錄, 因此0x801D - 0x8002 = 0x001B,代表該頁中實際的記錄有27條記錄
  • 00 00 -> 指向頁中空閑位置(偏移量)
  • 00 00 -> PAGE_GARBAGE 表示沒有刪除的數據
  • 16 4A -> PAGE_LAST_INSERT 最后插入記錄的位置偏移 0x18000 + 0x164A = 0x1964A 直接指向最后一行數據存儲的地址,也就是id為199,這條確實是最后一條插入的


    test表中最后一條記錄圖
  • 00 02 -> PAGE_DIRECTION 最后插入的方向,向右邊插入
  • 00 1A -> PAGE_N_DIRECTION 一個方向連續插入記錄的數量 連續插入26個
  • 00 1B -> PAGE_N_RECS 當前數據頁中含有27條記錄
  • 00 00 00 00 00 00 00 00 修改當前頁的最大事務ID
  • 00 00 -> 代表頁為葉子節點
  • 00 00 00 00 00 00 00 43 -> 索引ID,表示當前頁屬于哪個索引
  • 00 00 00 42 00 00 00 02 00 F2 -> B+樹數據頁非葉子節點所在段的segment header。注意該值僅在樹的root頁中定義
  • 00 00 00 42 00 00 00 02 00 32 ->B+樹數據頁所在段的segment header。

小結一下
1.innodb在整個頁可以使用的空間當成heap,當需要插入記錄的時候,首先會檢查PAGE_FREE指向的空閑空間,若申請的空間小于等于該空間容量時,那么使用該空閑空間,否者從PAGE_HEAP_TOP指向的空閑空間進行分配
heap中存儲的記錄非物理連續的,只是邏輯上連續的,可用下圖表示
2.PAGE_LAST_INSERT、PAGE_DIRECTION、PAGE_N_DIRECTION主要使用來做頁分裂操作的


數據頁中行記錄存儲的排列方式

三、偽記錄分析Infimum + Supremum Record (26字節-兩個虛擬行記錄)

innodb存儲引擎有兩個偽記錄,用來界定行記錄的邊界
數據從 0x1805E 到 0x18077


  • 01 00 02 00 1E -> recorder header (5字節)
  • 69 6E 66 69 6D 75 6D 00 -> 只有一個列的偽記錄,記錄內容就是Infimum(多了一個 0x00 字節) (8字節)
  • 08 00 0B 00 00 -> recorder header (5字節)
  • 73 75 70 72 65 6D 75 6D -> 只有一個列的偽記錄,記錄內容就是Supremum (8字節)
分析下偽記錄中的recorder header中的next_record

recorder header最后兩個字節 0x001E,表示下一個記錄位置的偏移量,即當前行記錄“內容”的位置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;

創建數據的腳本
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

由上面偽記錄Infimum的Record Header可知下一條記錄的開始地址是0x18081,順便把前面的extra info也分析下,截圖如下


  • AD 80 01 -> 變長字段長度列表, 逆序,第一列是1字節,第四列是2字節,所以第一列包含1個字符,第四列包含173個 字符80 AD存的是補碼,換算成原碼為0x00AD,轉換成10進制就是173
  • 02 -> 二進制(00000010)表示第二個字段為null


  • 00 00 10 00 CC -> Record Header 固定5字節長度
  • 00 00 00 AD -> 由于是自動創建的int自增id ,固是4個字節,當前行記錄id為173, 由于該id是無符號的,所以最高位不是符號位
  • 00 00 00 00 69 E7 -> TransactionId
  • D7 00 00 01 5C 01 10 -> Roll Pointer
  • 61 -> 第一列字段數據 a
  • 80 00 00 AD -> 第二列,存的是補碼,因此原碼為0xAD,故值為173
  • 第三列為null,不占用空間
  • 61 .... 61 -> 第四列字段數據 a ... a 173個 省略

分析User Record中Record Header中的內容

0x 00 00 10 00 CC 轉換成十進制如下 00000000 00000000 00010000 00000000 11001100
下面都是二進制的,其他的都是十六進制的

  • 0 -> 預留位1
  • 0 -> 預留位2
  • 0 -> delete_mask 標記該記錄是否刪除,0表示沒有刪除 說明刪除的數據很可能還在頁中,并且占用著空間
  • 0 -> min_rec_mask 標記該記錄是否為B+樹的非葉子節點中的最小記錄
  • 0000 -> n_owned 表示當前槽管理的記錄數
  • 00000000 00010 -> heap_no 表示當前記錄在記錄堆的位置信息,這個值表示當前記錄在heap中的位置為2
  • 000 -> record_type 表示當前記錄的類型,0表示普通記錄
  • 00000000 11001100 -> next_record 表示下一條記錄的相對位置,轉換16進制為0xCC,0x18081 + 0xCC = 0x1814D,下一條記錄的值地址為0x1814D,截圖如下


簡單用圖可表示如下(忽略實際內容):


五、分析Page Directory

這一頁的末尾是0x1BFFF,并且加上Page Header中PAGE_N_DIR_SLOTS,能夠知道Page Directory中包含了7個slot 截圖如下


位置是從 0x1BFEA - 0x1BFF7,一共14個字節,因此展開如下:

  • 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個字節,并且是在頁尾部,可以直接得出位置為0x1BFF8 開始的

  • 72 08 C8 7F -> Old-style Checksum
  • 00 38 23 77 -> Low 32 bit of LSN

為了保證頁能夠完整地寫入磁盤(如可能發生的寫入過程中磁盤損壞、機器宕機等原因),InnoDB存儲引擎的頁中設置了File Trailer部分。File Trailer只有一個FIL_PAGE_END_LSN部分,占用8個字節。前4個字節代表該頁的checksum值,最后4個字節和File Header中的FIL_PAGE_LSN相同。通過這兩個值來和File Header中的FIL_PAGE_SPACE_OR_CHKSUM和FIL_PAGE_LSN值進行比較,看是否一致(checksum的比較需要通過InnoDB的checksum函數來進行比較,不是簡單的等值比較),以此來保證頁的完整性(not corrupted)。

數據頁格式

File Header

名稱 大?。ㄗ止潱?/th> 說明
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+樹的特性就決定了葉子節點必須是雙向列表
FIL_PAGE_NEXT 4 當前頁的下一頁
FIL_PAGE_LSN 8 該值代表頁最后被修改的日志序列位置LSN
FIL_PAGE_TYPE 2 INNODB 存儲頁的類型,
FIL_PAGE_FILE_FLUSH_LSN 8 該值僅在系統表空間的一個頁中定義,代表文件至少更新到了LSN值。對于獨立表空間,該值為0
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID 4 從mysql4.1開始,該值代表屬于哪個表空間

Innodb存儲引用中頁的類型

名稱 十六進制 解釋
FIL_PAGE_INDEX 0x45BF B+樹葉節點
FIL_PAGE_UNDO_LOG 0x0002 undo log頁
FIL_PAGE_INODE 0x0003 索引節點
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 系統頁
FIL_PAGE_TYPE_TRX_SYS 0x0007 事務系統數據
FIL_PAGE_TYPE_FSP_HDR 0x0008 File space Header
FIL_PAGE_TYPE_XDES 0x0009 擴展描述頁
FIL_PAGE_TYPE_BLOB 0x000A BLOB頁

Page Header

用來數據頁的狀態信息,14部分組成,共56字節

名稱 大?。ㄗ止潱?/th> 說明
PAGE_N_DIR_SLOTS 2 在Page Directory (頁目錄〉中 的Slot (槽〉 數,“4.4.S Page Directory” 小節中會介紹
PAGE HEAP TOP 2 堆中第一個記錄的指針, 記錄在頁中是根據堆 的形式存放的 堆中空閑空間的位置(偏移量)
PAGE N HEAP 2 堆中的記錄數. 一共占用2 字節, 但是第15 位表示行記錄格式 (包括最小和最大記錄以及標記為刪除的記錄)
PAGE FREE 2 指向可重用空間的首指針 指向頁中空閑空間的位置(偏移量)(就是標記為刪除的記錄地址)
PAGE GARBAGE 2 己刪除記錄的字節 數, 即行記錄結構中也陽也在為1的記錄大小的總數
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 一個方向連續插入記錄的數量
PAGE N RECS 2 該頁中記錄的數量
AGE MAX TRX ID 8 修改當前頁 的最大事務ID, 注意該值僅在Secondary Index中定義
PAGE LEVEL 2 當前頁 在索引樹中的位置, OxOO代表葉節點, l!P時節 J點總是在第0層
PAGE INDEX ID 8 索引ID, 表示當前頁屬于哪個索引
PAGE BTR SEG LEAF 10 B+樹數據頁非頁節點所在段的segment header。 注意該值僅在 B+ 樹的 Root 頁中定義
PAGE BTR SEG TOP 10 B+樹數據頁所在段的 segment header. 注意該值僅在 B+樹的 Root 頁中定義

COMPACT行記錄格式

COMPACT行記錄格式
名稱 大?。╞it) 描述
預留位1 1 沒有使用
預留位2 1 沒有使用
delete_mask 1 標記該記錄是否被刪除
min_rec_mask 1 標記該記錄是否為B+樹的非葉子節點中的最小記錄
n_owned 4 表示當前槽管理的記錄數
heap_no 13 表示當前記錄在記錄堆的位置信息
record_type 3 表示當前記錄的類型,0表示普通記錄,1表示B+樹非葉節點記錄,2表示最小記錄,3表示最大記錄
next_record 16 表示下一條記錄的相對位置
Total 40(Byte) nothing

參考借鑒:

InnoDB數據頁結構
MYSQL內核:INNODB存儲引擎 卷1-4
MySQL技術內幕InnoDB存儲引擎第2版

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容