likes
comments
collection
share

一文了解MySQL数据页

作者站长头像
站长
· 阅读数 19

一、简介

为了避免一条一条读取磁盘数据,InnoDB采取的方式,作为磁盘内存之间交互基本单位。一个页的大小一般是16KB

InnoDB为了不同的目的而设计了多种不同类型的页

【比如】存放表空间头部信息的页、存放undo日志信息的页等等。我们把存放表中数据记录的页,称为索引页or数据页。 

创建一张学生信息表

一文了解MySQL数据页

二、数据页结构概览

InnoDB数据页结构示意图

一文了解MySQL数据页

三、记录在页中的存储

我们平时都是以记录为单位向表中插入数据的,这些记录在磁盘上的存放形式也被称为行格式or记录格式

记录在页中的存储

一文了解MySQL数据页

在一开始生成页的时候,没有UserRecords部分

当插入一条记录时,就会从Free Space中申请一个记录大小的空间,并将这个空间划分到User Records部分

当Free Space部分的空间全部都被User Records部分替代掉后,则这个页使用完了,如果再有新的记录插入,则需要去申请新的页了。

四、记录头信息

COMPACT行格式示意图

一文了解MySQL数据页

deleted_flag:删除标记(0:未删除 1:已删除 )。为什么被删除的记录还在页中?或者说,依然在磁盘上?

答:这些被删除的记录之所以没有从磁盘上删除,是因为如果移除了,还需要在磁盘上重新排列剩余的记录,这会带来一定的性能消耗,所以只是打了一个删除的标记就可以避免重排。然后所有的被删除掉的记录会组成一个垃圾链表,记录在这个链表中占用的空间被称为可重用空间。之后若是有新的记录插入到表中,它们就可以覆盖掉被删除的这些记录占用的存储空间了

min_rec_flag:B+树中每层非叶子节点中的最小的目录项记录,都会添加该标记。

n_owned:一个页面被分若干组后,“带头大哥”用于保存组中所有的记录条数

heap_no:表示当前记录在页面堆中的相对位置。 什么叫页面堆?heap_no作用是什么?

答:我们向表中插入的记录都会放到User Record部分,这些记录一条条的连续排列着,InnoDB将此连续排列的结构称之为堆(heap)。为了方便管理,他们把一条记录在堆中的相对位置称之为heap_no。堆中记录的heap_no值在分配之后就不会发生改动了,即使删除了堆中的某条记录,这条被删除记录的heap_no值页仍然保持不变。

为什么用户记录的heap_no从2开始?见下图学生信息表

因为创建页时,每个页会自动添加两条记录,且都没有主键值: 第一条代表页面中的最小记录(即:比任何用户记录都小)——Infimum记录,heap_no=0 第二条代表页面中的最大记录(即:比任何用户记录都大)——Supremum记录,heap_no=1 为了区分这两条默认记录和用户自己插入的记录,将着两条记录放到一个称为Infimum+Supremum的部分。

record_type:表示当前的记录类型

0:普通记录 1: B+树非叶子节点的目录项记录 2:表示Infimum记录 3:表示Supremum记录

next_record:表示下一条记录的相对位置。就是链表。这个属性非常重要。它表示从当前记录的真实数据到下一条记录的真实数据的距离。如下所示:

一文了解MySQL数据页

为什么要指向「记录头信息」和「真实数据之间」 的位置呢?而不是指向整条记录的开头位置?

答:因为这个位置刚刚好,向左读取就是记录头信息;向右读取就是真实数据

该属性为正数——说明当前记录的下一条记录在它的后面。 该属性为负数——说明当前记录的下一条记录在它的前面

比如:一条记录的next_record值为32,意味着从当前记录的真实数据的地址处向后找32字节便是下一条记录的真实数据。其中:「下一条记录」指的是按主键值由小到大的顺序排列的下一条记录。

通过下图,可以看出记录是按照主键从小到大的顺序形成了一个单向链表。记录被删除对next_record的影响,如下图所示:

一文了解MySQL数据页

  • deleted_flag变为了1,但是并没有从磁盘中删除。
  • next_record变为了0,意味着没有下一条记录了。
  • “bob”的next_record指向了“john”。
  • supremum记录的n_owned变为了4。
  • 如果再次执行插入操作 insert into tb_student values(3, 300, "tom", 16);时,InnoDB**不会因此申请新的存储空间**,而是直接恢复原来被删除记录的存储空间。

五、页目录Page Directory

记录在页中是按照主键值从小到大的顺序串联成为一个单向链表。那么如果我们要查询id=4的数据,用笨方法就是从记录的链表头开始,一直往下查找。但是如果数据量很大,那么性能就无法保证了。针对这个问题,InnoDB采取了图书目录的解决方案,即:Page Directory。生成Page Directory步骤如下:

首先,将非删除的数据(包含Infimum记录和Supremum记录)划分几个组。

【分组规则如下】

  • 对于Infimum记录所在的分组只能有1条记录
  • 对于Supremum记录所在的分组只能在1~8条记录之间
  • 剩下的记录所在的分组只能在4~8条记录之间

【分组步骤如下】

  • 初始情况下,一个数据页中只有Infimum记录和Supremum记录这两条,所以分为两个组。
  • 之后每当插入一条记录时,都会从页目录中找到对应记录的主键值比待插入记录的主键值大,并且差值最小的槽,然后把该槽对应的n_owned加1。
  • 当一个组中的记录数等于8时,当再插入一条记录的时候,会将组中的记录拆分成两个组(一个组中4条记录,另一个组中5条记录)。并在拆分过程中,会在Page Directory中新增一个槽,并记录这个新增分组中最大的那条记录的偏移量。

每个组的最后一条记录(即:也是这个组里,最大的那条记录)——“带头大哥”,其余的记录均为“组内小弟”;“大哥”记录的头信息中的n_owned属性表示该组内共有几条记录,而“小弟”的n_owned属性都为0;

将“大哥”在页面中的地址偏移量取出来,按顺序存储到靠近Page Trailer的地方。这个地方就是Page Directory

Page Directory中的这些地址偏移量被称为Slot),每个槽占用2个字节

一个正常的页面为16KB,即:16384字节。而2个字节可以表示的地址偏移量范围是0~(2^16-1),即:0~65535。所以2个字节表示一个槽足够了。

Page Directory就是由多个槽组成的。 记录和页目录的关系,如下所示,分为2组。

一文了解MySQL数据页

页目录生成完毕后,则可以通过二分法快速进行查找。在一个数据页中查找指定主键值的记录时,过程分为两步:

  • 第一步:通过二分法确定该记录所在分组对应的Slot,然后找到该Slot所在分组中主键值最小的那条记录。每个槽对应的都是组内主键值最大的记录,那么怎么定位一个组中主键值最小的记录呢? 答:由于每个槽都是挨着的,所以,我们可以通过找到前一个槽中的最大主键值记录,这个记录的下一条记录(next_record),就是本槽的最小主键值记录。
  • 第二步:通过记录的next_record属性遍历该槽所在组中的各个记录