InnoDB 数据页结构
InnoDB 数据页类型
页是 InnoDB 管理存储空间的基本单元,默认大小为 16KB。
注意:这 16KB 是连续的存储空间。
在 InnoDB 中有很多种类型的页,如下表所示:
数据页
我们要重点掌握的是存储记录的页,官方称之为索引页(FIL_PAGE_INDEX),我们一般叫它数据页,其结构如下图所示:
下面详细介绍下其中几个组成部分:
File Header
File Header 用来记录页的一些通用信息,固定占用 38 个字节,由下面这些部分组成:
我们来了解下 File Header 中几个重要的属性:
FIL_PAGE_SPACE_OR_CHKSUM
这个值是当前页的校验和(checksum),checksum 是一种数据校验方法,用于检查数据在传输或存储过程中是否发生变化。
它的原理是:使用算法将大数据量的原始数据计算出一个小数据量的值,即 checksum,并与原始数据一起传输或存储。传输或存储结束时,使用原始数据重新计算 checksum 并跟传输过来的 checksum 或之前存储的 checksum 比较,以判断数据是否完整。
checksum 类似 hash,区别是 hash 更安全,因为 checksum 通常使用较简单的算法,而 hash 通常使用更复杂的算法。
因此,checksum 通常用于数据传输和存储等要求高效率的场景,hash 通常用于密码存储、数字签名等追求安全的场景。
FIL_PAGE_OFFSET
页在表空间中的偏移量,也叫页号,从 0 开始,依次递增,如:
1. 表空间的第一个页,FIL_PAGE_OFFSET 的值为 0
2. 表空间的第二个页,FIL_PAGE_OFFSET 的值为 1
3. 如果某个数据页是表空间中的第 1000 个页,那么这个页的 FIL_PAGE_OFFSET 字段的值应该是 999,这意味着该数据页位于表空间文件从起始位置偏移 999 * 16KB 字节的位置(页默认为 16KB 大小)
FIL_PAGE_TYPE
页类型,详见文章开头的表格。
FIL_PAGE_PREV 和 FIL_PAGE_NEXT
我们知道 InnoDB 索引是 B+ 树结构,B+ 树的叶子节点组成了双向链表结构,这个双向链表就是使用 FIL_PAGE_PREV 和 FIL_PAGE_NEXT 实现的。
有些类型的页没有这两个属性
Page Header
这个部分是数据页独有的,用来记录数据页的状态信息,固定占用 56 个字节,由下面这些部分组成:
PAGE_DIRECTION 和 PAGE_N_DIRECTION
假如新插入的一条记录的索引值比上一条记录的索引值要大,我们就说这条记录的插入方向是右边(PAGE_RIGHT),反之则是左边(PAGE_LEFT)。
如果连续几次插入新记录的方向都是一样的,就把这个次数存储到PAGE_N_DIRECTION,直到后续出现不一样的插入方向则清零重新计数。
Infimum Record + Supremum Record
在 InnoDB 中,每个数据页都有两个虚拟记录:Infimum Record 和 Supremum Record。
Infimum Record 是最小记录,比当前页中的任何记录都要小;Supremum Record 是最大记录,比当前页中的任何记录都要大;
这两个记录在页创建时就被创建出来,并且不能被删除。
下面是一个简化的抽象结构,表明了Infimum Record、Supremum Record和普通用户记录之间的关系:
Page Directory
InnoDB 数据页中的记录会分成多个组,一个组可以包含多条记录,组中最后一条记录的 n_owned 存储了该组的记录数。Page Directory(页目录) 存储了每组最后一条记录的偏移量,这些偏移量也被称为目录槽(Directory Slots),有了这些槽,就可以利用二分法来加速页内记录的查找。
InnoDB 对每个组的记录数有如下规定:
Infimum记录所在组只能有 1 条记录,即最小记录的 n_owned 总是为 1
Supremum 记录所在槽可以有 1 ~ 8 条记录,即最大记录的 n_owned 取值范围为 [1, 8]
其他组可以有 4 ~ 8 条记录,即其他用户记录 n_owned 取值范围为 [4, 8]
当插入或删除记录时,可能触发组的分裂或平衡。
记录分组的过程如下:
最初,数据页只有 2 条记录,会分成 2 个组:Infimum 记录所在组和 Supremum 记录所在组
之后,每插入一条记录,都会从页目录中找到合适的槽,再将记录添加到该槽所在组,并将槽对应记录的 n_owned 加 1
如果在一个 n_owned 等于 8 的组中再添加一条记录,就会创建一个新的组,将这 9 条数据分配到这两个组中,一个组存放 4 条记录,一个组存放 5 条记录,同时也会在页目录中创建一个新的槽
在数据页内查找某个索引值对应记录的过程如下:
使用二分法确定目标记录所在槽
通过记录的next_record遍历该槽
下面是一个页目录和页中记录的关系示例图:
File Trailer
为了校验页的完整性,InnoDB 在页最后设计了一个 File Trailer 部分,固定占用 8 个字节,分成 2 个小部分:
(1) 前 4 个字节存储:checksum
每当一个页在内存中被修改了,就会重新计算checksum,更新File Header 和 File Trailer 中存储的 checksum。
当需要把内存中的页同步到磁盘上时,首先把File Header同步过去,如果同步后面的数据失败了(如断电),则磁盘中对应页的 File Trailer 中的 checksum 还是由修改前的数据计算的,与 File Header 中的 checksum 不一致,说明页不完整,同步出现了问题。
(2) 后 4 个字节存储:最后被修改时的LSN
最后被修改时的LSN(Log Sequence Number)也用于校验页的完整性,在 File Header 中也存储了一份,同步完需要比较两个 LSN 是否一致。