一文了解MySQL数据页
一、简介
为了避免一条一条读取磁盘数据,InnoDB采取页的方式,作为磁盘和内存之间交互的基本单位。一个页的大小一般是16KB。
InnoDB为了不同的目的而设计了多种不同类型的页。
【比如】存放表空间头部信息的页、存放undo日志信息的页等等。我们把存放表中数据记录的页,称为索引页or数据页。
创建一张学生信息表
二、数据页结构概览
InnoDB数据页结构示意图
三、记录在页中的存储
我们平时都是以记录为单位向表中插入数据的,这些记录在磁盘上的存放形式也被称为行格式or记录格式。
记录在页中的存储
在一开始生成页的时候,没有UserRecords部分。
当插入一条记录时,就会从Free Space中申请一个记录大小的空间,并将这个空间划分到User Records部分。
当Free Space部分的空间全部都被User Records部分替代掉后,则这个页使用完了,如果再有新的记录插入,则需要去申请新的页了。
四、记录头信息
COMPACT行格式示意图
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:表示下一条记录的相对位置。就是链表。这个属性非常重要。它表示从当前记录的真实数据到下一条记录的真实数据的距离。如下所示:
为什么要指向「记录头信息」和「真实数据之间」 的位置呢?而不是指向整条记录的开头位置?
答:因为这个位置刚刚好,向左读取就是记录头信息;向右读取就是真实数据
该属性为正数——说明当前记录的下一条记录在它的后面。 该属性为负数——说明当前记录的下一条记录在它的前面。
比如:一条记录的next_record值为32,意味着从当前记录的真实数据的地址处向后找32字节便是下一条记录的真实数据。其中:「下一条记录」指的是按主键值
由小到大的顺序排列
的下一条记录。
通过下图,可以看出记录是按照主键从小到大的顺序形成了一个单向链表。记录被删除对next_record
的影响,如下图所示:
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
组。
页目录生成完毕后,则可以通过二分法快速进行查找。在一个数据页中查找指定主键值的记录时,过程分为两步:
- 第一步:通过二分法确定该记录所在分组对应的Slot,然后找到该Slot所在分组中主键值最小的那条记录。每个槽对应的都是组内主键值最大的记录,那么怎么定位一个组中主键值最小的记录呢? 答:由于每个槽都是挨着的,所以,我们可以通过找到前一个槽中的最大主键值记录,这个记录的下一条记录(next_record),就是本槽的最小主键值记录。
- 第二步:通过记录的next_record属性遍历该槽所在组中的各个记录
转载自:https://juejin.cn/post/7168822175427330062