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

名称 大小(字节) 说明
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字节

名称 大小(字节) 说明
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行记录格式
名称 大小(bit) 描述
预留位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版

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容