大家好,上篇文章说了InnoDB中记录的存储结构,今天来讲讲InnoDB的数据页的结构。首先我们要了解什么是数据页,它是干什么用的。

当我们创建一个存储引擎为InnoDB的mysql数据库时,数据库里的数据信息是存储在磁盘上的,但是真正处理数据的过程是发生在内存中的,所以在我们对数据进行读写操作时,数据便会在磁盘和内存之间来回传递,InnoDB会把数据划分成若干个页,以页为基本单位将数据在磁盘和内存之间进行交互。InnoDB中页的大小一般是16KB。InnoDB有许多不同的页,比如存放表空间头部信息的页、存放Change Buffer信息的页、存放InnoDB信息的页、存放undo日志信息的页等,今天我们只浅谈一下存放表中数据的页,也就是数据页。

mysql innodb 特性_数据

InnoDB数据页的存储空间大致分为7部分,具体信息如下所示:

名称

中文名

占用空间

简单描述

File Header

文件头部

38 字节

页的一些通用信息

Page Header

页面头部

56 字节

数据页专有的一些信息

Infimum + Supremum

页面中的最小记录和最大记录

26 字节

两个虚拟的行记录

User Records

用户记录

不确定

实际存储的行记录内容

Free Space

空闲空间

不确定

页中尚未使用的空间

Page Directory

页目录

不确定

页中的某些记录的相对位置

File Trailer

文件尾部

8 字节

校验页是否完整

了解了页的大致结构以后,我们来讲一下记录在数据页中是如何存储的,话不多说,直接上图:

mysql innodb 特性_主键_02

一开始新生成的数据页是没有User Records (用户记录)部分的,当有记录插入时,就会从Free Space(空闲空间)部分申请一部分空间来存储插入的记录,随着插入记录的增多,Free Space(空闲空间)部分的空间会全部被User Records (用户记录)部分替代,这个时候再新插入数据的时候便会新产生一个页。

上述过程就是记录存储到页的整体过程,是不是觉得很简单,下面我们再往更细的方面聊一聊。

上篇文章我们讲了一下InnoDB的行格式,其中有一部分叫记录头信息,这部分信息在记录存储到数据页的过程中有着至关重要的作用。下图是COMPACT行格式的结构图以及记录头信息部分的结构图,上篇文章有相关讲解,想了解的可以去看一下。

mysql innodb 特性_主键_03

接下来我们将行格式简化一下,只显示本次讲的重要部分,简化后的结构如下图所示:

mysql innodb 特性_数据_04

delete_mask(delete_flag): 这个属性标记着当前记录是否被删除,占用1个二进制位,值为0的时候代表记录并没有被删除,为1的时候代表记录被删除。(当我们删除一条记录时,这条记录并不会从磁盘中消失,只是打上删除标记)

min_rec_mask(min_rec_flag): B+树的每层非叶子节点中的最小记录都会添加该标记,之后会聊到,今天先不管这块。

n_owned: 为了方便寻找页中的某条记录,InnoDB将页中的记录进行分组,每组最后一条记录会将本组的记录数(不包括delete_mask为1的记录)存到n_owned中,组内其余的记录n_owned全部存0。

heap_no: 记录会在在数据页的User Records (用户记录)部分紧密的排列在一起,这些排列在一起的记录结构称为堆,heap_no是这条记录在堆中的相对位置。

注意:每个页中heap_no值为0和1的两条记录是固定的,0为页面中最小的记录(Infimum记录),1为页面中最大的记录(Supremum记录),这两条记录不是用户插入的真实记录,而是页自带的且每个页的这两条记录是相同的。

mysql innodb 特性_数据_05

record_type: 表示当前记录的类型,0表示普通记录,1表示B+树非叶节点记录,2表示最小记录,3表示最大记录。

next_record: 表示从当前记录的真实数据到下一条记录的真实数据的距离,整数表示下一条记录在当前记录前面,负数则反之。

注意:记录的前后顺序和插入的先后顺序无关,而是按照主键大小的顺序进行排列的。最小记录永远是第一条,最大记录永远是最后一条。

mysql innodb 特性_mysql innodb 特性_06

介绍完简化后记录头信息各个部分的作用后,我们就可以讲一下记录在页中具体的存储结构。

在上述内容讲到n_owned属性时,我们知道为方便查找记录,InnoDB将记录进行了分组,在分好组后,每组记录的最大偏移量会以槽的形式存到数据页中的Page Directory部分。在这里,假设我们往一张表中存了4条真实记录,那么InnoDB会将他们分为两组存放到页中,具体结构如下图所示:

mysql innodb 特性_数据库_07

在上图中,我们要了解以下几点:

  1. 最小记录所在的组只能有一条数据,就是最小记录本身。
  2. 最大记录所在的组记录条数必须是1-8条之间。
  3. 其他记录所在的组记录条数必须是4-8条之间。当组中记录超过8条时,会将组拆分成两个组,一个组4条,一个组5条,然后新生成的组会在数据页中的Page Directory部分新增一个槽,用来存放新生成组的最大记录的偏移量。

现在,我们假设数据页中有18条记录,它们分成了5组,以下图为例讲解一下InnoDB在页中查找记录的过程。

mysql innodb 特性_mysql innodb 特性_08

当我们想查找图中ID为6的记录时,过程是这样的:

  1. 计算中间槽的位置:(0+4)/2=2 ,所以查看槽2对应记录的主键值为 8 ,又因为 8 > 6 ,所以设置 high=2 , low 保持不变。
  2. 重新计算中间槽的位置:(0+2)/2=1 ,所以查看槽1对应的主键值为 4 ,又因为 4 < 6 ,所以设置 low=1 , high 保持不变。
  3. 因为 high - low 的值为1,所以确定主键值为 6 的记录在槽2对应的组中。此刻我们可以拿到槽1对应的记录(主键值为4),然后向下找到槽2中主键值最小的那条记录,然后沿着单向链表遍历槽2中的记录。直到找到主键值为6的那条记录即可。

总结:在一个数据页中查找指定主键值的记录的过程分为两步:
1. 通过二分法确定该记录所在的槽,并找到该槽中主键值最小的那条记录。
2. 通过记录的 next_record 属性遍历该槽所在的组中的各个记录。

最后我们再简单说一下数据页结构中的 Page Header(页面头部)File Header(文件头部) 部分

Page Header(页面头部) 用来存储数据页中存储的记录的状态信息,比如本页中已经存储了多少条记录,第一条记录的地址是什么,页目录中存储了多少个槽等等,这个部分占用固定的56个字节,专门存储各种状态信息,具体各个字节都是干嘛的如下所示:

名称

占用空间

描述

PAGE_N_DIR_SLOTS

2 字节

在页目录中的槽数量

PAGE_HEAP_TOP

2 字节

还未使用的空间最小地址,也就是说从该地址之后就是Free Space

PAGE_N_HEAP

2 字节

本页中的记录的数量(包括最小和最大记录以及标记为删除的记录)

PAGE_FREE

2 字节

第一个已经标记为删除的记录地址(各个已删除的记录通过next_record也会组成一个单链 表,这个单链表中的记录可以被重新利用

PAGE_GARBAGE

2 字节

已删除记录占用的字节数

PAGE_LAST_INSERT

2 字节

最后插入记录的位置

PAGE_DIRECTION

2 字节

记录插入的方向

PAGE_N_DIRECTION

2 字节

一个方向连续插入的记录数量

PAGE_N_RECS

2 字节

该页中记录的数量(不包括最小和最大记录以及被标记为删除的记录)

PAGE_MAX_TRX_ID

8 字节

修改当前页的最大事务ID,该值仅在二级索引中定义

PAGE_LEVEL

2 字节

当前页在B+树中所处的层级

PAGE_INDEX_ID

8 字节

索引ID,表示当前页属于哪个索引

PAGE_BTR_SEG_LEAF

10 字节

B+树叶子段的头部信息,仅在B+树的Root页定义

PAGE_BTR_SEG_TOP

10 字节

B+树非叶子段的头部信息,仅在B+树的Root页定

File Header(文件头部) 是专门针对数据页记录的各种状态信息,比如页里头有多少个记录,页目录有多少个槽。不同类型的页都会以File Header 作为第一个组成部分,它描述了一些针对各种页都通用的一些信息,这个部分占用固定的38个字节,是由下边这些内容组成的:

名称

占用空间

描述

FIL_PAGE_SPACE_OR_CHKSUM

4 字节

页的校验和(checksum值)

FIL_PAGE_OFFSET

4 字节

页号

FIL_PAGE_PREV

4 字节

上一个页的页号

FIL_PAGE_NEXT

4 字节

下一个页的页号

FIL_PAGE_LSN

8 字节

页面被最后修改时对应的日志序列位置(英文名是:Log Sequence Number)

FIL_PAGE_TYPE

2 字节

该页的类型

FIL_PAGE_FILE_FLUSH_LSN

8 字节

仅在系统表空间的一个页中定义,代表文件至少被刷新到了对应的LSN值

FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID

4 字节

页属于哪个表空间

到此,InnoDB数据页结构介绍完毕,本篇文章内容有点多,讲的不好的地方烦请大家多多指教,有什么问题欢迎大家在评论区进行讨论