数据库 -- InnoDB数据页结构

InnoDB是一个将表中的数据存储到磁盘上的存储引擎,采取的方式是:将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,InnoDB中页的大小一般为 16KB。
数据库 -- InnoDB数据页结构
数据库 -- InnoDB数据页结构
1. File Header
一个页只有16KB,可能需要好多页来存放数据,FIL_PAGE_PREV 和 FIL_PAGE_NEXT 这两个属性就分别代表本页的上一个和下一个页的页号。注:并不是所有类型的页都有上一个和下一个页的属性,故所有的数据页其实是一个双链表。
数据库 -- InnoDB数据页结构
2. Page Header
数据页中存储的记录的状态信息,比如本页中已经存储了多少条记录,第一条记录的地址是什么,页目录中存储了多少个槽等。

3. User Records
我们自己存储的记录会按照我们指定的行格式存储到User Records部分。但刚开始并没有User Records这个部分,每当我们插入一条记录,都会从Free Space部分申请一个记录大小的空间划分到User Records部分,当Free Space部分的空间全部被User Records部分替代掉之后,也就意味着这个页使用完了,如果还有新的记录插入的话,就需要去申请新的页了。
数据库 -- InnoDB数据页结构
每条记录的行格式简化图为:
数据库 -- InnoDB数据页结构
当向表中插入几条记录时,为方便分析这些记录在页的User Records中是怎么表示的,下图把记录中头信息和实际的列数据都用十进制表示出来(其实是一堆二进制位),各条记录在User Records中存储时并没有空隙,为了观看方便才把每条记录单独画在一行中。
数据库 -- InnoDB数据页结构
各个属性含义:

  • delete_mask: 标记当前记录是否被删除,值为0的时候代表记录并没有被删除,为1的时候代表记录被删除掉了。被删除的记录之所以不立即从磁盘上移除,是因为移除它们之后把其他的记录在磁盘上重新排列需要性能消耗,所以只是打个删除标记而已,而且这部分存储空间之后还可以重用。
  • min_rec_mask:这个属性标记该记录是否为B+树的非叶子节点中的最小记录
  • n_owned:记录该组内共有几条记录
  • heap_no:表示当前记录在本页中的位置,值为0和1的记录:分别代表“最小纪录”和“最大纪录”,被单独放在一个Infimum + Supremum部分,记录可以比大小,对于一条完整的记录来说, 比较记录的大小就是比较主键的大小。
  • record_type:表示当前记录的类型,0表示普通记录,1表示B+树非叶节点记录,2表示最小记录,3表示最大记录。
  • next_record:它表示从当前记录的真实数据到下一条记录的真实数据的地址偏移量。下一条记录指得并不是我们插入顺序的下一条记录,而是按照主键值由小到大的顺序的下一条记录。
    数据库 -- InnoDB数据页结构
    上图是把记录2删掉的示意图,不论我们怎么对页中的记录做增删改操作,InnoDB始终会维护一条记录的单链表,链表中的各个节点是按照主键值由小到大的顺序连接起来的。

4. Page Directory

  • 将所有正常的记录(包括最大和最小记录,不包括为已删除的记录)划分为几个组。

  • 每个组的最后一条记录的头信息中的n_owned属性表示该组内共有几条记录。

  • 将每个组的最后一条记录的地址偏移量按顺序存储起来,每个地址偏移量也被称为一个槽。这些地址偏移量都会被存储到靠近页的尾部的地方。
    数据库 -- InnoDB数据页结构设计InnoDB的大叔们对每个分组中的记录条数是有规定的,对于最小记录所在的分组只能有 1 条记录,最大记录所在的分组拥有的记录条数只能在 1~8 条之间,剩下的分组中记录的条数范围只能在是 4~8 条之间。所以分组是按照下边的步骤进行的:

  • 初始情况下一个数据页里只有最小记录和最大记录两条记录,它们分属于两个分组。

  • 之后每插入一跳记录都放到最大记录所在的组,直到最大记录所在组中的记录数等于8个。

  • 在最大记录所在组中的记录数等于8个的时候再插入一条记录时,将最大记录所在组平均分裂成2个组。
    数据库 -- InnoDB数据页结构
    所以在一个数据页中查找指定主键值的记录的过程分为两步:

  • 通过二分法确定该记录所在的槽。

  • 通过记录的next_record属性组成的链表遍历查找该槽中的各个记录。