MySQL是怎样运行的(五)——InnoDB数据页结构

5.1 不同类型的页简介

​ InnoDB 为了不同的目的而设计了多种不同类型的页,比如存放表空间头部信息的页、存放Change Buffer信息的页、存放INODE信息的页,存放undo日志信息的页等等。那些存放表中记录的那种类型的页,官方称为索引页。鉴于上述还未介绍索引,目前先将存放记录的页称为数据页。

5.2 数据页结构快览

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

5.3 记录在页中的存储

5.3.1 记录头信息的秘密

create table page_demo(
	c1 int, c2 int , c3 varchar(10000), primary key(c1)
)charset=ascii row_format=compact;

5.4 Page Directory(页目录)

  • 将所有正常的记录(包括Infimum和Supremum记录,但不包括已经移除到垃圾链表的记录)划分为几个组。
  • 每个组的最后一条记录(也就是组内最大的那条记录)相当于“带头大哥”,组内其余的记录相当于“小弟”。“带头大哥”记录的头信息中的n_owned属性表示该组内共有几条记录。
  • 将每个组中最后一条记录在页面中的地址偏移量(就是该记录的真是数据与页面中第0个字节之间的距离)单独提取出来,按顺序存储到靠近页尾部的地方。这个地方就是Page Directory。页目录中的这些地址偏移量称为槽(Slot),每个槽占用2字节。页目录就说由多个槽组成的。

一个正常的页面也就是16KB大小,即16384字节,而2字节可用表示的地址偏移量范围是0~65535,所以用2字节表示一个槽足够了。

分组的记录数是有规定的:对于Infimum记录所在的分组只能有1条记录,也就是它自己,Supremum记录所在的分组拥有的记录条数只能在18条之间,剩下的分组中的记录条数范围只能是在48条之间。所以给记录进行分组是按照下面的步骤进行的:

  1. 在初始情况下,一个数据页中只有Infimum记录和Supremum记录这两条,他们分属于两个分组。页目录中也只有两个槽,分别代表Infimum记录和Supremum记录在页面中的地址偏移量。
  2. 之后每插入一条记录,都会从页目录中找到对应记录的主键值比待插入记录的主键值大并且差值最小的槽(从本质上来说,槽是一个组内最大的记录在页面中的地址偏移量,通过槽可以快速找到对应的记录的主键值),然后把该槽对应的记录的n_owned值加1,表示本组内又添加了一条记录,知道该组中的记录数等于8个。
  3. 当一个组中的记录数等于8后,再插入一条记录,会将组中的记录拆分成两个组,其中一个组中4条记录,另外一个5条记录。这个拆分过程会在页目录中新增一个槽,记录这个新增分组中最大的那条记录的偏移量。

在一个数据页中查找指定主键值的记录时,过程分为两步。

  1. 通过二分法确定该路所在分组对应的槽,然后找到该槽所在分组中主键值最小的那条记录。
  2. 通过记录的next_record属性遍历该槽所在的组中的各个记录。

5.5 Page Header(页面头部)

为了能得到存储在数据页中的记录的状态信息,比如数据页中已经存储了多少条记录,FreeSpace在页面中的地址偏移量、页目录中存储了多少个槽等,特意在数据页中定义了一个名为Page Header的部分,它是也结构的第二部分,占用固定的56字节,专门存储各种状态信息。

状态名称占用空间大小描述
PAGE_N_DIR_SLOTS2字节在页目录中的槽数量
PAGE_HEAP_TOP2字节还未使用的空间的最小地址,也就是说从该地址之后就是free space
PAGE_N_HEAP2字节第一位表示本记录是否为紧凑型的记录,剩余的15为表示本页的堆中记录的数量
(包括Infimum,Supremum记录以及标记为“已删除”的记录)
PAGE_FREE2字节各个已删除的记录通过next_record 组成一个单向链表,
这个单向链表中的记录所占用的存储空间可以被重新利用;PAGE_FREE表示该链表头节点对应记录在页面中的偏移量
PAGE_GARBAGE2字节已删除记录占用的字节数
PAGE_LAST_INSERT2字节最后插入记录的位置
PAGE_DIRECTION2字节记录插入的方向
PAGE_N_DIRECTION2字节一个方向连续插入的记录数量
PAGE_N_RECS2字节该页中用户记录的数量(不包括infimum和supremum记录以及被删除的记录)
PAGE_MAX_TRX_ID8字节修改当前页的最大事务id,该值仅在二级索引页面中定义
PAGE_LEVEL2字节当前页在B+树中所处的层级
PAGE_INDEX_ID8字节索引ID,表示当前页属于哪个索引
PAGE_BTR_SEG_LEAF10字节B+树叶子节点段的头部信息,仅在B+树的根页面中定义
PAGE_BTR_SEG_TOP10字节B+树非叶子节点段的头部信息,仅在B+树的根页面中定义
  • PAGE_DIRECTION:假如新插入的一条记录的主键值比上一条记录的主键值大,我们说这条记录的插入方向是右边,繁殖则是左边。原来表示最后一条记录插入 方向的状态就是PAGE_DIRECTION。
  • PAGE_N_DIRECTION:假设连续几次插入新记录的方向都是一致的,innodb会把沿着同一个方向插入记录的条数记下来,这个条数就用PAGE_N_DIRECTION状态表示。

5.6 File Header(文件头部)

​ File Header通用于各种类型数据的页,也就是说各种类型的页都会以File Header作为第一个组成部分,它描述了一些通用于各种页的信息,比如这个页的编号是多少,它的上一页和下一页是谁,等等。该部分占用固定的38字节

状态名称占用空间大小描述
FIL_PAGE_SPACE_OR_CHKSUM4字节当MySQL的版本低于4.0.14时,该属性表示本页面所在的表空间ID;在之后的版本中,该属性表示页的校验和(checksum)
FIL_PAGE_OFFSET4字节页号
FIL_PAGE_PREV4字节上一个页的页号
FIL_PAGE_NEXT4字节下一个页的页号
FIL_PAGE_LSN8字节页面被最后修改时对应的LSN(Log Sequence Number, 日志序号列)值
FIL_PAGE_TYPE2字节该页的类型
FIL_PAGE_FILE_FLUSH_LSN8字节仅在系统表空间的第一个页中定义,代表文件至少被刷新到了对应的LSN值
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID4字节页属于哪个表空间
  • FIL_PAGE_SPACE_OR_CHKSUM:表示当前页面的校验和。什么是校验和?就是对于一个很长的字符串来说,我们会通过某种算法计算出一个比较短的值来代表这个很长的字符串,这个比较短的值就称为校验和。比较两个很长的字符串之前,先比较这两个长字符串的校验和,节省直接比较两个长字符串的时间损耗。
  • FIL_PAGE_OFFSET: 每一个页都有一个单独的页号,如同我们的身份证号码一样。InnoDB通过页号来唯一定位一个页。
  • FIL_PAGE_TYPE:表示当前页的类型。前文说过,InnoDB为了不同的目的而把页分为不同的类型,我们前面介绍的都是存储记录的数据页,其实还有很多其他类型的页:
类型名称十六进制描述
FIL_PAGE_TYPE_ALLOCATED0x0000最新分配,还未使用
FIL_PAGE_UNDO_LOG0x0002undo日志页
FIL_PAGE_INODE0x0003存储段的信息
FIL_PAGE_IBUF_FREE_LIST0x0004change buffer空闲列表
FIL_PAGE_IBUF_BITMAP0x0005change buffer 的一些属性
FIL_PAGE_TYPE_SYS0x0006存储一些系统数据
FIL_PAGE_TYPE_TRX_SYS0x0007事务系统数据
FIL_PAGE_TYPE_FSP_HDR0x0008表空间头部信息
FIL_PAGE_TYPE_XDES0x0009存储区的一些属性
FIL_PAGE_TYPE_BLOB0x000A溢出页
FIL_PAGE_INDEX0x45BF索引页,也就是我们所说的数据页

​ 用来存放记录的数据页的类型其实是FIL_PAGE_INDEX, 也就是索引页,至于啥是索引,且听下回分解。

​ 前面在说记录的存储结构时,所说的溢出页的类型是FIL_PAGE_TYPE_BLOB,而存放正常记录的页面类型是FIL_PAGE_INDEX,两者是不一样的。

  • FIL_PAGE_PREV、FIL_PAGE_NEXT: InnoDB可能无法一次性为大数据分配一个很大的存储空间,而如果分散到多个不连续的页中进行存储,则需要把这些页关联起来。这样通过建立一个双向链表就把许多的页串联起来了, 而无需这些页在物理上真正连着。值得注意的是, 并不是所有类型的页都有上一页与下一页的属性。

5.7 File Trailer (文件尾部)

​ 我们知道,InnoDB存储引擎会把数据存储到磁盘上,但是磁盘速度太慢,需要以页为单位把数据加载到内存中处理。如果该页中的数据在内存中被修改了, 那么在修改后的某个时间还需要把数据刷新到磁盘中,但是如果在刷新还没有结束的时候断电了该咋办?为了检测一个页是否完整,InnoDB在每个页的尾部都加了一个File Trailer部分,这个部分由8字节组成,可以分成2个小部分。

  • 前4字节代表页的校验和。这个部分与File Header中的校验和相对应。每当一个页面在内存中发生修改时,在刷新之前就要把页面的校验和算出来。因为FileHeader在页面的前边,所以File Header中的校验和会被首先刷新到磁盘,当完全写完后,校验和也会被写道页的尾部。如果页面书信成功,则页号和页尾的校验和应该是一致的。如果刷新了一部分后断电了,那么File Header中的校验和就代表着已经修改列过的页,而File Trailer中的校验和代表着原先的页,二者不同则意味着刷新期间发生了错误。
  • 后4字节代表页面被最后修改时对应的LSN的后4字节,正常情况下应该与FileHeader部分FIL_PAGE_LSN的后4字节相同。这个部分也是用于校验页的完整性,不过我们目前还没说LSN是什么意思,所以大家可以先不用管这个属性。

File Trailer与File Header类似,都通用于所有类型的页。

5.8 总结

InnoDB为了不同的目的而设计了不同类型的页,我们把用于存放记录的页称为数据页。一个数据页可以被大致分为7个部分,分别如下:

  • File Header:表示页的一些通用信息,占固定的38字节。
  • Page Header:表示数据页专有的一些信息,占固定的56字节
  • Infimum + Supremum:两个虚拟的伪记录,分别表示页中的最小记录和最大记录,占固定的26字节
  • User Records:真正存储我们插入的记录,大小不固定
  • Free Space:页中尚未使用的部分,大小不固定
  • Page Directory:页中某些记录的相对位置,也就是各个槽对应的记录在页面中的地址偏移量;大小不固定,插入的记录越多,这个部分占用的空间就越多。
  • File Trailer:用于检验页是否完整,占固定的8字节

​ 每个记录的头信息中都有一个next_record属性,从而可以使页中的所有记录串联成一个单向链表。
​ InnoDB会把页中的记录划分为若干组,每个组的最后一个记录的地址偏移量作为一个槽,存放在Page Directory中,一个槽占用2字节。在一个页中根据主键查找记录是非常快的,分为两步。

1. 通过二分法确定记录所在分组对应的槽,并找到该槽所在分组中主键值最小的那条记录。
2. 通过记录的next_record 属性遍历该槽所在组的各个记录

​ 每个数据页的File Header部分都有上一个页和下一个页的编号,所以所有的数据页会组成一个双向链表。
​ 在将页的内存刷新到磁盘时,为了保证页的完整性,页首和页尾都会存储页中数据的校验和,以及页面最后修改时对应的LSN值(页尾只会存储LSN值的后4字节)。如果页首和页尾的校验和以及LSN值校验不成功,就说明刷新期间出现了问题。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值