【MySQL是怎样运行的-读书笔记】第5章 盛放记录的大盒子-InnoDB数据页结构

1.不同类型的页简介

前边简单提了一下页的概念,它是InnoDB 管理存储空间的基本单位,一个页的大小一般是16KB 。InnoDB 为了不同的目的而设计了多种不同类型的页,我们只聚焦那些存放我们表中记录的那种类型的页,官方称这种存放记录的页为索引( INDEX )页,鉴于我们还没有了解过索引是个什么东西,而这些表中的记录就是我们日常口中所称的数据,所以目前还是叫这种存放记录的页为数据页

2.数据页结构的快速浏览

数据页代表的这块16KB 大小的存储空间可以被划分为多个部分,不同部分有不同的功能,各个部分如图所示:

image-20221008125448247

这些概念先混个脸熟,之后具体问题具体分析。

3.记录在页中的存储

在页的7个组成部分中,我们自己存储的记录会按照我们指定的行格式存储到User Records 部分。但是在一开始生成页的时候,其实并没有User Records 这个部分,每当我们插入一条记录,都会从Free Space 部分,也就是尚未使用的存储空间中申请一个所插入的这条记录的大小的空间划分到User Records 部分,当Free Space 部分的空间全部被User Records 部分替代掉之后,也就意味着这个页使用完了,如果还有新的记录插入的话,就需要去申请新的页了,这个过程的图示如下:

image-20221008125800353

为了更好的管理在User Records 中的这些记录, InnoDB 可费了一番力气,在哪费力气了呢?不就是把记录按照指定的行格式一条一条摆在User Records 部分么?其实这话还得从记录行格式的记录头信息中说起。

记录头信息的秘密

第4章 从一条记录说起-InnoDB记录结构中我们只对记录头信息做了简单介绍,现在深入一点分析一下。

为了故事的顺利发展,我们先创建一个表:

mysql> CREATE TABLE page_demo(
-> c1 INT,
-> c2 INT,
-> c3 VARCHAR(10000),
-> PRIMARY KEY (c1)
-> ) CHARSET=ascii ROW_FORMAT=Compact;
Query OK, 0 rows affected (0.03 sec)

这个新创建的page_demo 表有3个列,其中c1 和c2 列是用来存储整数的, c3 列是用来存储字符串的。需要注意的是,我们把 c1 列指定为主键,所以在具体的行格式中InnoDB就没必要为我们去创建那个所谓的 row_id 隐藏列了。而且我们为这个表指定了ascii 字符集以及Compact 的行格式。所以这个表中记录的行格式示意图就是这样的:

image-20221008130248253

为了大家理解上的方便,下面我们只在page_demo 表的行格式演示图中画出有关的头信息属性以及c1 、c2 、c3 列的信息(其他信息没画不代表它们不存在啊,只是为了理解上的方便在图中省略了~),简化后的行格式示意图就是这样:

image-20221008130630567

下边我们试着向page_demo 表中插入几条记录:

mysql> INSERT INTO page_demo VALUES(1, 100, 'aaaa'), (2, 200, 'bbbb'), (3, 300, 'cccc'),
(4, 400, 'dddd');
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0

为了方便分析这些记录在页的User Records 部分中是怎么表示的,我把记录中头信息和实际的列数据都用十进制表示出来了(其实是一堆二进制位),所以这些记录的示意图就是:

image-20221008130715406

对照着这个图来看看记录头信息中的各个属性是啥意思:

  • delete_mask

    这个属性标记着当前记录是否被删除,占用1个二进制位,值为0 的时候代表记录并没有被删除,为1 的时候代表记录被删除掉了。

    被删除的记录还在页中么?是的,摆在台面上的和背地里做的可能大相径庭,你以为它删除了,可它还在真实的磁盘上。

    这些被删除的记录之所以不立即从磁盘上移除,是因为移除它们之后把其他的记录在磁盘上重新排列需要性能消耗,所以只是打一个删除标记而已,所有被删除掉的记录都会组成一个所谓的垃圾链表,在这个链表中的记录占用的空间称之为所谓的可重用空间,之后如果有新记录插入到表中的话,可能把这些被删除的记录占用的存储空间覆盖掉。

    小贴士:
    将这个delete_mask位设置为1和将被删除的记录加入到垃圾链表中其实是两个阶段,在介绍事务的时候会详细说明删除操作的详细过程。

  • min_rec_mask

    B+树的每层非叶子节点中的最小记录都会添加该标记。如果不清楚什么是B+ 树可以先在相关专栏查阅相关资料。反正我们自己插入的四条记录的min_rec_mask 值都是0 ,意味着它们都不是B+ 树的非叶子节点中的最小记录。

  • n_owned

    这个暂时保密,稍后它是主角

  • heap_no

    这个属性表示当前记录在本页中的位置,从图中可以看出来,我们插入的4条记录在本页中的位置分别
    是: 2 、3 、4 、5 。是不是少了点啥?是的,怎么不见heap_no 值为0 和1 的记录呢?

    设计自动给每个页里边儿加了两个记录,由于这两个记录并不是我们自己插入的,所以有时候也称为伪记录或者虚拟记录。这两个伪记录一个代表最小记录,一个代表最大记录,等一下哈~,记录可以比大小么?

    是的,记录也可以比大小,对于一条完整的记录来说,比较记录的大小就是比较主键的大小。比方说我们插入的4行记录的主键值分别是: 1 、2 、3 、4 ,这也就意味着这4条记录的大小从小到大依次递增。

    小贴士:
    请注意我强调了对于一条完整的记录来说,比较记录的大小就相当于比的是主键的大小。后边我们还会介绍只存储一条记录的部分列的情况。

    这两条记录的构造十分简单,都是由5字节大小的记录头信息和8字节大小的一个固定的部分组成的,如图所示:

    image-20221008131355798

    由于这两条记录不是我们自己定义的记录,所以它们并不存放在页的User Records 部分,他们被单独放在一个称为Infimum + Supremum 的部分,如图所示:

    image-20221008131419111

    从图中我们可以看出来,最小记录和最大记录的heap_no 值分别是0 和1 ,也就是说它们的位置最靠前(heap_no表示当前记录在本页中的位置)。

  • record_type

    这个属性表示当前记录的类型,一共有4种类型的记录, 0 表示普通记录, 1 表示B+树非叶节点记录, 2 表示最小记录, 3 表示最大记录。从图中我们也可以看出来,我们自己插入的记录就是普通记录,它们的record_type 值都是0 ,而最小记录和最大记录的record_type 值分别为2 和3 。
    至于record_type 为1 的情况,我们之后在说索引的时候会重点强调的。

  • next_record

    这玩意儿非常重要,它表示从当前记录的真实数据到下一条记录的真实数据的地址偏移量。比方说第一条记录的next_record 值为32 ,意味着从第一条记录的真实数据的地址处向后找32 个字节便是下一条记录的真实数据。如果你熟悉数据结构的话,就立即明白了,这其实是个链表,可以通过一条记录找到它的下一条记录。但是需要注意注意再注意的一点是, 下一条记录指得并不是按照我们插入顺序的下一条记录,而是按照主键值由小到大的顺序的下一条记录(即这个链表维持着有序状态,按照主键从小到大排序。新插入的记录不是直接插到链表尾部,而是通过比较插入到合适的位置)。而且规定 Infimum记录(也就是最小记录) 的下一条记录就是本页中主键值最小的用户记录,而本页中主键值最大的用户记录的下一条记录就是 Supremum记录(也就是最大记录) ,为了更形象的表示一下这个next_record 起到的作用,我们用箭头来替代一下next_record 中的地址偏移量:

    image-20221008131927001

    可以看出明显的单链表结构,现在如果删除第2条记录

    image-20221008132121520

    删除第2条记录前后主要发生了这些变化:

    • 第2条记录并没有从存储空间中移除,而是把该条记录的delete_mask 值设置为1 。
    • 第2条记录的next_record 值变为了0,意味着该记录没有下一条记录了。
    • 第1条记录的next_record 指向了第3条记录。
    • 还有一点你可能忽略了,就是最大记录的n_owned 值从5 变成了4 ,关于这一点的变化我们稍后会详细说明的。

    所以,不论我们怎么对页中的记录做增删改操作,InnoDB始终会维护一条记录的单链表,链表中的各个节点是按照主键值由小到大的顺序连接起来的。

    小贴士:

    你会不会觉得next_record这个指针有点儿怪,为啥要指向记录头信息和真实数据之间的位置呢?为啥不干脆指向整条记录的开头位置,也就是记录的额外信息开头的位置呢?

    因为这个位置刚刚好,向左读取就是记录头信息,向右读取就是真实数据。我们前边还说过变长字段长度列表、NULL值列表中的信息都是逆序存放,这样可以使记录中位置靠前的字段和它们对应的字段长度信息在内存中的距离更近,可能会提高高速缓存的命中率。

    再来看一个有意思的事情,因为主键值为2 的记录被我们删掉了,但是存储空间却没有回收,如果我们再次把这条记录插入到表中,会发生什么事呢?

    image-20221008132415784

    从图中可以看到, InnoDB 并没有因为新记录的插入而为它申请新的存储空间,而是直接复用了原来被删除记录的存储空间。

    小贴士:
    当数据页中存在多条被删除掉的记录时,这些记录的next_record属性将会把这些被删除掉的记录组成
    一个垃圾链表,以备之后重用这部分存储空间。

4.Page Directory(页目录)

现在我们了解了记录在页中按照主键值由小到大顺序串联成一个单链表,那如果我们想根据主键值查找页中的某条记录该咋办呢?

最笨的办法:从Infimum 记录(最小记录)开始,沿着链表一直往后找,这种遍历查找这是一个笨办法。

我们平常想从一本书中查找某个内容的时候,一般会先看目录,找到需要查找的内容对应的书的页码,然后到对应的页码查看内容。设计InnoDB 的大叔们为我们的记录也制作了一个类似的目录,他们的制作过程是这样的:

  1. 将所有正常的记录(包括最大和最小记录,不包括标记为已删除的记录)划分为几个组
  2. 每个组的最后一条记录(也就是组内最大的那条记录)的头信息中的n_owned 属性表示该记录拥有多少条记录,也就是该组内共有几条记录。
  3. 将每个组的最后一条记录的地址偏移量单独提取出来按顺序存储到靠近页的尾部的地方,这个地方就是所谓的Page Directory ,也就是页目录(此时应该返回头看看页面各个部分的图)。页面目录中的这些地址偏移量被称为槽(英文名: Slot ),所以这个页面目录就是由槽组成的。

比方说现在的page_demo 表中正常的记录共有6条, InnoDB 会把它们分成两组,第一组中只有一个最小记录,第二组中是剩余的5条记录,看下边的示意图:

image-20221008135634800

设计人员对每个分组中的记录条数是有规定的:对于最小记录所在的分组只能有 1 条记录,最大记录所在的分组拥有的记录条数只能在 1~8 条之间,剩下的分组中记录的条数范围只能在是 4~8 条之间。

Page Header(页面头部)

设计InnoDB 的大叔们为了能得到一个数据页中存储的记录的状态信息,比如本页中已经存储了多少条记录,第一条记录的地址是什么,页目录中存储了多少个槽等等,特意在页中定义了一个叫Page Header 的部分,它是页结构的第二部分,这个部分占用固定的56 个字节,专门存储各种状态信息,具体各个字节都是干嘛的看下表:

image-20221008140300530

这里我们先说明一下PAGE_DIRECTION 和PAGE_N_DIRECTION 的意思:

  • PAGE_DIRECTION

    假如新插入的一条记录的主键值比上一条记录的主键值大,我们说这条记录的插入方向是右边,反之则是左边。用来表示最后一条记录插入方向的状态就是PAGE_DIRECTION 。

  • PAGE_N_DIRECTION

一下PAGE_DIRECTION 和PAGE_N_DIRECTION 的意思:

  • PAGE_DIRECTION

    假如新插入的一条记录的主键值比上一条记录的主键值大,我们说这条记录的插入方向是右边,反之则是左边。用来表示最后一条记录插入方向的状态就是PAGE_DIRECTION 。

  • PAGE_N_DIRECTION

    假设连续几次插入新记录的方向都是一致的, InnoDB 会把沿着同一个方向插入记录的条数记下来,这个条数就用PAGE_N_DIRECTION 这个状态表示。当然,如果最后一条记录的插入方向改变了的话,这个状态的值会被清零重新统计。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值