InnoDB为不同目的设计了很多不同类型的页,比如undo日志信息的页,存放ChangBuffer的页,而存放表中数据记录的页我们称之为数据页。

1、数据页结构

名称 中文名 占用空间大小 描述
File Header 文件头部 38字节 页的一些通用信息
Page Header 页面头部 56字节 数据页专有的一些信息
Infimum + Supremum 最小记录和最大记录 26字节 两个虚拟的行记录
User Records 用户记录 不确定 实际存储的行记录内容
Free Space 空闲空间 不确定 页中尚未使用的空间
Page Directory 页面目录 不确定 页中的某些记录的相对位置
File Trailer 文件尾部 8字节 校验页是否完整

2、记录在页中的存储

存储的记录会按照指定的行格式存储到 User Records中,一开始页生成时没有User Records部分,每当插入一条记录时从Free Space部分申请一个记录大小的空间,将其划分到User Records,当Free Space全部被User Records代替后就意味着该页已经写满,如果此时还有新记录插入,就需要去申请新的页了。

重新看下记录头,并详细解释部分项含义

名称 大小(位) 描述
预留位1 1 没有使用
预留位2 1 没有使用
delete_flag 1 标识当前记录是否被删除
min_rec_flag 1 B+树的每层非叶子节点中的最小记录都会添加该标记
n_owned 4 数据页会将记录分组,组内地址偏移量最大的记录会统计该组的记录数
heap_no 13 表示当前记录在页面堆中的相对位置。
record_type 3 当前记录的类型,0表示普通记录,1表示B+树非叶子节点记录,2表示最小记录(Inflimum),3表示最大记录(Superemum)。
next_record 16 下一条记录的相对位置

delete_flag
delete_flag 值为1表示删除,删除之后的记录任然保存在磁盘中,如果将记录从磁盘移除,需要重新漂亮其他记录,会带来性能消耗问题,所以只需要打上删除标记,被删除的记录会形成一个垃圾链表,记录在这个链表中的空间称之为可重用空间,之后若是有新记录插入就有可能覆盖掉被删除记录占用的空间


heap_no
数据页User Records一条一条数据记录称为堆,记录在堆中的相对位置称之为heap_no,页前面记录的heap_no较小,页后面的heap_no较大。InnoDB在每个页面中加入了两个虚拟记录,一条代表页面的最小记录(Infimum记录),一条代表页面的最大记录(Supremun记录),Infimum记录的heap_no为0,Supremun记录的heap_no为1。


next_record
数据页中的记录是一个按照主键值由小到大顺序串联的单向链表


n_owned
为了方便根据主键查找页中的某条记录,数据页会将记录分组,组内地址偏移量最大的记录会统计该组的记录数放在n_owned字段,分组相关详情参考页目录内容

3、页目录

记录在页中是一个按照主键值由小到大顺序串联的单向链表。为了方便根据主键查找页中的某条记录,创建了页目录的结构:
将所有的正常记录(包括Inflimum和Superemum记录,但不包括已经移到垃圾链表的记录)划分为几个组 ,每个组的最后一条记录的头信息n_owned属性表示该组共有几条记录,将每个组最后一条记录在页面的偏移地址即槽(2字节)单独提取出来,按顺序存放在靠近页尾部的地方,该地方即为页目录。


分组说明

分组规定
对于Infimum记录所在分组只能有一条记录,Supremun记录所在分组记录数在1~8条间,剩下分组的记录数只能在4~8条间。

分组过程
1、初始情况下,一个数据页只有Infimum和Supremun两条记录,它们分属两个分组,页目录也只有两个槽,分别为Infimum和Supremun记录在页面的地址偏移量
2、之后每插入一条记录,都会从页目录中找出对应记录主键值比待插入数据主键值大并且差值最小的槽,然后把槽对应记录的记录头n_owned加1,表示本组内又添加了一条数据。
3、当组内记录数等于8后,再插入一条记录,会将组内记录拆分为两组,其中一个组4条,一个组5条,然后在页目录新增一个槽,记录这个新增分组中id最大记录的地址偏移量。


有了页目录我们使用主键查找某条记录就方便的多,以主键为查询条件对页目录按槽进行二分查找,查找到主键对应槽后,遍历槽对应分组记录。

二分查找过程即计算中间槽位置,对比中间槽记录对应主键和查找主键值,再决定查找最小槽到中间槽区间还是中间槽到最大槽区间,重新计算中间槽,直到定位到一个前一个槽主键值小于查找主键值,后一个槽主键值大于等于查找主键值的分组。

4、Page Header 页面头部

页面头部记录了存储在数据页中记录的一些状态信息。

属性名称 占用空间大小 描述
PAGE_N_DIR_SLOTS 2字节 在页目录中的槽数量
PAGE_HEAP_TOP 2字节 还未使用的空间最小地址,也就是说从该地址之后就是Free Space
PAGE_N_HEAP 2字节 第1位表示本记录是否为紧凑型的记录,剩余的15位表示本页的堆中记录的数量(包括Infimum和Supremum记录以及标记为“已删除”的记录)
PAGE_FREE 2字节 各个已删除的记录通过next_record 组成个单向链表,这个单向链表中的记录所占用的存储空间可以被重新利用,PAGE_FREE 表示该链表头节点对应记录在页面
中的偏移量
PAGE_GARBAGE 2字节 已删除记录占用的字节数
PAGE_LAST_INSERT 2字节 最后插入记录的位置
PAGE_DIRECTION 2字节 记录插入的方向
PAGE_N_DIRECTION 2字节 一个方向连续插入的记录数量
PAGE_N_RECS 2字节 该页中用户记录的数量(不包括Inimum和Supremum
记录以及被删除的记录)
PAGE_MAX TRX_ID 8字节 修改当前页的最大事务id,该值仅在二级索引页面中定义
PAGE LEVEL 2字节 当前页在B+树中所处的层级
PAGE_INDEX_ID 8字节 索引ID,表示当前页属于哪个索引
PAGE_BTR_SEG_LEAF 10字节 B+树叶子节点段的头部信息,仅在B+树的根页面中定义
PAGE BTR_ SEG_TOP 10字节 B+树非叶子节点段的头部信息,仅在B+树的根页面中定义

5、File Header 文件头部

FileHeader通用于各种类型的页,描述了通用于各种页的信息。

FileHeader结构及描述

属性名称 占用空间大小 描述
FIL_PAGE_SPACE_OR_CHKSUM 4字节 当MySQL的版本低于4.0.14日时,该属性表
示本页面所在的表空间ID;在之后的版本
中,该属性表示页的校验和(checksum)
FIL_PAGE_OFFSET 4字节 页号
FIL_PAGE_PREV 4字节 上一个页的页号
FIL_PAGE_NEXT 4字节 下一个页的页号
FIL_PAGE_LSN 8字节 页面被最后修改时对应的LSN ( Log Sequene
Number,日志序列号) 值
FIL_PAGE_TYPE 2字节 该页的类型
FILPAGE_FILE_FLUSH_LSN 8字节 仅在系统表空间的第一个页中定义,代表文件至少被刷新到了对应的LSN值
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID 4字节 页属于哪个表空间

其它类型页

类型名称 十六进制 描述
FIL_PAGE_TYPE_ALLOCATED 0x0000 最新分配,还未使用
FILPAGE_UNDO LOG 0x0002 undo日志页
FIL_PAGE_INODE 0x0003 存储段的信息
FIL_PAGE_IBUF_FREE LIST 0x0004 Change Buffer空闲列表
FIL_PAGE_IBUF_BITMAP 0x0005 Change Buffer的一些属性
FIL_PAGE_TYPE_SYS 0x0006 存储一些系统数据
FIL_PAGE_TYPE_TRX_SYS 0x0007 事务系统数据
FIL_PAGE_TYPE_FSP_HDR 0x0008 表空间头部信息
FIL_PAGE_TYPE_XDES 0x0009 存储区的一-些属性
FIL_PAGE_TYPE_BLOB 0x000A 溢出页
FIL_PAGE_INDEX 0x45BF 索引页,也就是我们所说的数据页

6、File Trailer 文件尾部

File Trailer 用来校验页面完整性,避免部分刷盘,File Trailer 由8个字节组成,可以分为两个部分

  • 前4个字节代表页的校验和
    • 该校验和和File Header的校验和相对应。页面在刷盘之前,先将页面的校验和算出来,因为File Header在页面的前边,所以FileHeader中的校验和会先刷盘(操作系统页面大小为4k,InnoDB页面大小16k,所以需要多次刷盘),File Trailer 中的校验和最后刷盘,如果页面刷盘成功,则页首和页尾的校验和应该是一致的。
  • 后4个字节代表页面被最后修改时对应LSN的后4个字节,正常情况下应该与File Header的FIL_PAGE_LSN的后4个字节相同,该部分也是为了验证页面完整性。