MySQL InnoDB数据页

MySQL InnoDB数据页

一个数据页就相当于B+树中的一个节点。页是 MySQL 中磁盘和内存交互的基本单位,也是 MySQL 是管理存储空间的基本单位。一个页一般是 16KB ,当记录中的数据太多,当前页放不下的时候,会把多余的数据存储到其他页中,这种现象称为行溢出 。

一个页由7个部分组成

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

User Records 用户记录区

是存储实际数据的地方,在页的7个组成部分中,我们自己存储的记录会按照我们指定的行格式存储到User Records部分。但是在一开始生成页的时候,其实并没有User Records这个部分,每当我们插入一条记录,都会从Free Space部分,也就是尚未使用的存储空间中申请一个记录大小的空间划分到User Records部分,当Free Space部分的空间全部被User Records部分替代掉之后,也就意味着这个页使用完了,如果还有新的记录插入的话,就需要去申请新的页了。

记录头信息

在用户数据区中,记录头信息作为行格式的一部分。有下面几部分:

delete_mask:这个属性标记着当前记录是否被删除,占用1个二进制位,值为0的时候代表记录并没有被删除,为1的时候代表记录被删除掉了。 min_rec_mask:B+树的每层非叶子节点中的最小记录都会添加该标记。 n_owned:该组内共有几条记录 heap_no:这个属性表示当前记录在本中的位置。(MySQL会在位置0和1生成最小和最大的伪记录,并不在User Records而是在Infimum + Supremumrecord_type:这个属性表示当前记录的类型,一共有4种类型的记录,0表示普通记录,1表示B+树非叶节点记录,2表示最小记录,3表示最大记录。record_type1的表示目录项记录 next_record:表示从当前记录的真实数据到下一条记录的真实数据的地址偏移量。比方说第一条记录的next_record值为32,意味着从第一条记录的真实数据的地址处向后找32个字节便是下一条记录的真实数据。其实是个链表,可以通过一条记录找到它的下一条记录。但是需要注意注意再注意的一点是,下一条记录指得并不是按照我们插入顺序的下一条记录,而是按照主键值由小到大的顺序的下一条记录。而且规定 Infimum记录(也就是最小记录) 的下一条记录就是本页中主键值最小的用户记录,而本页中主键值最大的用户记录的下一条记录就是 Supremum记录(也就是最大记录)

Infimum + Supremum

26 个字节 两个虚拟的伪记录,分别表示页中的最小和最大记录,占固定的 26 个字节。

Page Directory 页目录

查询某条记录,最笨的办法:从Infimum记录(最小记录)开始,沿着链表一直往后找,总有一天会找到

InnoDB为记录设计了一个目录:

  1. 将所有正常的记录(包括最大和最小记录,不包括标记为已删除的记录)划分为几个组。
  2. 每个组的最后一条记录(也就是组内最大的那条记录)的头信息中的n_owned属性表示该记录拥有多少条记录,也就是该组内共有几条记录。
  3. 将每个组的最后一条记录的地址偏移量单独提取出来按顺序存储到靠近的尾部的地方,这个地方就是所谓的Page Directory,也就是页目录(此时应该返回头看看页面各个部分的图)。页面目录中的这些地址偏移量被称为(英文名:Slot),所以这个页面目录就是由组成的。

Slot 槽

作用:将数据页内的数据分组管理,先定位到具体的槽再查到具体数据,加速二分查找。

槽位n_owned规则

  • Infimum槽:固定为1条记录
  • 普通槽:4-8条记录(满后分裂)
  • Supremum槽:1-8条记录(弹性变化)

槽位管理规则 当槽内记录达到8条时:

  • 触发分裂
  • 一般平均分配,每个槽4条记录 当槽内记录少于4条时:
  • 触发合并
  • 与相邻槽合并
  • 合并后如果超过8条,再次触发分裂

插入示例

最初始状态,是一个空表,每个数据页中都会默认包含两条系统记录

槽1:Infimum记录(n_owned=1) 
槽2:Supremum记录(n_owned=1)

插入第一条用户记录时

记录顺序:
Infimum -> R1(10) -> Supremum

槽位分布:
槽1:Infimum (n_owned=1)
槽2:R1(10), Supremum (n_owned=2)

再插入第2,3,4...7条数据,都会在槽2中,

槽1: Infimum (n_owned=1)
槽2: R1(10), R2(20), R3(30), R4(40), R5(50), R6(60), R7(70), Supremum (n_owned=8)

直到第8条数据,触发分裂。

槽1: Infimum (n_owned=1)  
槽2: R1(10), R2(20), R3(30), R4(40) (n_owned=4)  
槽3: R5(50), R6(60), R7(70), R8(80), Supremum (n_owned=5)

每插入一条记录,都会从页目录中找到主键值比本记录的主键值大并且差值最小的槽,然后把该槽对应的记录的n_owned值加1,表示本组内又添加了一条记录,直到该组中的记录数等于8个。

详细步骤说明: 假如现在要插入R9(15) R10(65)

插入R9(15)

步骤1: 二分查找页目录槽
比较槽尾记录值:
- 槽1尾记录: Infimum < 15
- 槽2尾记录: R4(40) > 15
- 槽3尾记录: Supremum > 15

找到槽2是大于15的最小差值槽

步骤2: R9(15)会插入到槽2中
记录顺序变为:
Infimum -> R1(10) -> R9(15) -> R2(20) -> R3(30) -> R4(40) -> R5(50)...

步骤3: 槽2的n_owned从4变为5
槽1: Infimum (n_owned=1)
槽2: R1,R9,R2,R3,R4 (n_owned=5)
槽3: R5,R6,R7,R8,Supremum (n_owned=5)

插入R10(65)

步骤1: 二分查找页目录槽
比较槽尾记录值:
- 槽1尾记录: Infimum < 65
- 槽2尾记录: R4(40) < 65
- 槽3尾记录: Supremum > 65

找到槽3是大于65的最小差值槽

步骤2: R10(65)会插入到槽3中
记录顺序变为:
...R4(40) -> R5(50) -> R6(60) -> R10(65) -> R7(70) -> R8(80) -> Supremum

步骤3: 槽3的n_owned从5变为6
槽1: Infimum (n_owned=1)
槽2: R1,R9,R2,R3,R4 (n_owned=5)
槽3: R5,R6,R10,R7,R8,Supremum (n_owned=6)

Page Header 页头

56个字节

名称占用空间大小描述
PAGE_N_DIR_SLOTS2字节在页目录中的槽数量
PAGE_HEAP_TOP2字节还未使用的空间最小地址,也就是说从该地址之后就是Free Space
PAGE_N_HEAP2字节本页中的记录的数量(包括最小和最大记录以及标记为删除的记录)
PAGE_FREE2字节第一个已经标记为删除的记录地址(各个已删除的记录通过next_record也会组成一个单链表,这个单链表中的记录可以被重新利用)
PAGE_GARBAGE2字节已删除记录占用的字节数
PAGE_LAST_INSERT2字节最后插入记录的位置
PAGE_DIRECTION2字节记录插入的方向
PAGE_N_DIRECTION2字节一个方向连续插入的记录数量
PAGE_N_RECS2字节该页中记录的数量(不包括最小和最大记录以及被标记为删除的记录)
PAGE_MAX_TRX_ID8字节修改当前页的最大事务ID,该值仅在二级索引中定义
PAGE_LEVEL2字节当前页在B+树中所处的层级
PAGE_INDEX_ID8字节索引ID,表示当前页属于哪个索引
PAGE_BTR_SEG_LEAF10字节B+树叶子段的头部信息,仅在B+树的Root页定义
PAGE_BTR_SEG_TOP10字节B+树非叶子段的头部信息,仅在B+树的Root页定义

File Header 文件头

38字节 Page Header是专门针对数据页记录的各种状态信息,比如数据页中的数据记录数量,槽的数量等;MySQL还有其他类型的页,File Header针对各种类型的页都通用,包括的信息如,页的编号,上个页和下个页的指针等。

名称占用空间大小描述
FIL_PAGE_SPACE_OR_CHKSUM4字节页的校验和(checksum值)
FIL_PAGE_OFFSET4字节页号
FIL_PAGE_PREV4字节上一个页的页号
FIL_PAGE_NEXT4字节下一个页的页号
FIL_PAGE_LSN8字节页面被最后修改时对应的日志序列位置(英文名是:Log Sequence Number)
FIL_PAGE_TYPE2字节该页的类型
FIL_PAGE_FILE_FLUSH_LSN8字节仅在系统表空间的一个页中定义,代表文件至少被刷新到了对应的LSN值
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID4字节页属于哪个表空间

Free Space 空闲空间

页中尚未使用的部分,大小不确定

File Trailer 页尾8个字节

用于检验页是否完整的部分,占用固定的8个字节。

updatedupdated2024-12-302024-12-30