03.InnoDB数据页结构

InnoDB数据页结构

在上一节中我们简单提了一下 页 的概念,它是 InnoDB 管理存储空间的基本单位,一个页的大小一般是 16KB 。这一节我们介绍存放表中数据的页,这种页也称为索引页,我们还不知道索引的意思,所以我们还是将这些存放记录的页称为数据页吧。

数据页大概结构

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

由上图可知一个数据页的存储空间被分为7个部分,下面是各个部分的简介:

image-20230914205243601

下面的部分将会对这些内容进行讲解。

记录在页中的数据

从上面的图可知,真正存储我们存入表中记录的部分是User Records部分。当表中没有数据时却并不存在User Records部分,每当我们插入数据时都会从Free Space(未使用的空间)申请一个记录大小的空间,并将这个空间划入User Records中。当 Free Space 部分的空间全部 被 User Records 部分替代掉之后,也就意味着这个页使用完了,如果还有新的记录插入的话,就需要去申请新 的页了。

这个过程如下图:

image-20230914205920956

而在这个简单的过程中还隐藏着很多的细节,我们先来看记录行格式的记录头信息说起。

记录信息头

为了介绍下面的内容,我们创建一个表:

mysql> CREATE TABLE page_demo(
 -> c1 INT,
 -> c2 INT,
 -> c3 VARCHAR(10000),
 -> PRIMARY KEY (c1)
 -> ) CHARSET=ascii ROW_FORMAT=Compact;

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

由此我们得到这个表中记录的行格式示意图:

image-20230914210451450

这里我们主要来了解记录头信息中的内容,我们先回顾记录头信息中的各个属性的意思:

image-20230914210751379

为了方便理解,我们只画出page_demo 的相关头信息和c1,c2,c3列的信息了。简化后如下:

image-20230914211138146

现在表中还并未插入数据,所以User Records 部分并不存在,下面我们将向表中插入一些数据:

mysql> INSERT INTO page_demo VALUES(1, 100, 'aaaa'), (2, 200, 'bbbb'), (3, 300, 'cccc'),
(4, 400, 'dddd');

此时页中User Records部分的示意图如下图:

image-20230914211533079

现在介绍记录信息头各个属性表示的意义:

  • delete_mask:这个属性标记着当前记录是否被删除,占用1个二进制位,值为 0 未删除,为 1 时代表记录被删除掉了。

    • 由此属性可知被删除数据仍会存在表中,这是因为将数据删除后需要重新排列别的数据造成性能消耗,所以记录仅仅是逻辑删除,删除掉的记录会组成一个垃圾链表,之后若有新记录插入可能会覆盖掉这些被删除掉的记录的空间(可重用空间)。
  • min_rec_mask:B+树的每层非叶子节点中的最小记录都会添加该标记(下一节介绍)。

  • n_owned:稍后介绍

  • heap_no:这个属性表示当前记录在本 页 中的位置,这个值是递增的,在前的记录较小,在后的较大。

    • 图中我们的四条行格式的该值为2,3,4,5,而没有0和1.
    • 这是因为为了方便管理InnoDB会向User Records中添加两条记录,可以将这两条记录称为伪记录 或者 虚拟记录。
      • Infimum,代表页中的最小记录。
      • Supermum,代表页中的最大记录。
    • 无论插入多少条记录,任何记录都比Infimum大,都比Supermum小。(比较的规则是主键)
    • InfimumSupermum记录的heap_no值为0和1,在堆中的位置靠前。
    • heap_no一经分配后就不会发生改变

    这两条伪记录的构造:

    image-20230914214258844
  • record_type:这个属性表示当前记录的类型,一共有4种类型的记录:

    • 0 表示普通记录。
    • 1 表示B+树非叶节点记录,这种情况在下一节介绍。
    • 2 表 示最小记录。
    • 3 表示最大记录。
  • next_record:表示从当前记录的真实数据到下一条记录的真实数据的距离。

    • 该值若为正数,说明下一条记录在该记录后面,若为负数说明下一条记录在该记录前面。
    • 我们可以知道这样的结构就是数据结构中我们很熟悉的链表,可以通过一条记录找到它的下一条记录。
      • 但下一条记录并不是指插入顺序中的下一条记录,而是按照主键值由小到大的顺序的下一条记录。
      • 也就是说,Infimum记录就是链表的头节点,Supermum记录是链表的尾节点,这条链表是按照主键递增的

    现在可知page_demo User Records部分如下图:

    image-20230914215525270

因为记录的结构类似于链表,所以它的删除操作与链表相似,并且会将删除记录的next_record属性置为0,当我们再次向表中插入该条数据时,InnoDB并不会为它申请新的空间,而是直接服用了原来被删除记录的存储空间。

Page Directory(页目录)

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

我们当然可以沿着单链表查询各个节点来找到目标记录,但这样效率非常低。

我们可以参照每本书的目录,先去查找页的目录,然后到对应的页去找到我们想要的记录。

创建页目录

这样的目录创建过程如下:

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

我们之前的page_demo 目前有6条记录,InnoDB 会把它们分成两组,第一组中只有一个最小记录, 第二组中是剩余的5条记录:

image-20230915211741180

注意

  • 现在 页目录 部分中有两个槽,也就意味着我们的记录被分成了两个组, 槽1 中的值是 112 ,代表最大记录 的地址偏移量(就是从页面的0字节开始数,数112个字节); 槽0 中的值是 99 ,代表最小记录的地址偏移 量。
  • n_owned属性:
  • 最小记录的 n_owned 值为 1 ,这就代表着以最小记录结尾的这个分组中只有 1 条记录,也就是最小记录 本身。
  • 最大记录的 n_owned 值为 5 ,这就代表着以最大记录结尾的这个分组中只有 5 条记录,包括最大记录本 身还有我们自己插入的 4 条记录。

我们将槽中的地址偏移量修改为指向对应记录的箭头并优化布局后可得:

image-20230915212053315

页记录分组规则

InnoDB 的分组规则:对于最小记录所在的分组只能有 1 条记录, 最大记录所在的分组拥有的记录条数只能在 1~8 条之间,剩下的分组中记录的条数范围只能在是 4~8 条之间。下面是分组的步骤:

  • 初始情况下一个数据页里只有最小记录和最大记录两条记录,它们分属于两个分组,页目录也就有两个槽。
  • 之后每插入一条记录,都会从 页目录 中找到主键值比本记录的主键值大并且差值最小的槽,然后把该槽对 应的记录的 n_owned 值加1,表示本组内又添加了一条记录,直到该组中的记录数等于8个。
  • 在一个组中的记录数等于8个后再插入一条记录时,会将组中的记录拆分成两个组,一个组中4条记录,另一 个5条记录。这个过程会在 页目录 中新增一个 槽 来记录这个新增分组中最大的那条记录的偏移量。

我们将表page_demo 中添加一些记录来插入记录后页中目录和行记录的变化:

mysql> INSERT INTO page_demo VALUES(5, 500, 'eeee'), (6, 600, 'ffff'), (7, 700, 'gggg'),
(8, 800, 'hhhh'), (9, 900, 'iiii'), (10, 1000, 'jjjj'), (11, 1100, 'kkkk'), (12, 1200, 'l
lll'), (13, 1300, 'mmmm'), (14, 1400, 'nnnn'), (15, 1500, 'oooo'), (16, 1600, 'pppp');

现在表中有18个记录,分为了5个组:

image-20230915213225697

现在页中的记录是按照主键递增的,所以我们建立的页目录中的槽也是递增的。所以我们在搜索的过程中不需要使用复杂度为O(n)的暴力搜索,我们可以使用复杂度更低的二分法

  • 先使用二分法找到记录所在的槽。

  • 槽中记录的是该槽中最大主键值的地址,但每个槽是连续的,我们可以找到该槽的上一槽,上一槽记录的地址又指向了本槽的最小小记录,我们就可以从这个最小值来遍历这个组来找到目标记录。
    我们建立的页目录中的槽也是递增的。所以我们在搜索的过程中不需要使用复杂度为O(n)的暴力搜索,我们可以使用复杂度更低的二分法

  • 先使用二分法找到记录所在的槽。

  • 槽中记录的是该槽中最大主键值的地址,但每个槽是连续的,我们可以找到该槽的上一槽,上一槽记录的地址又指向了本槽的最小小记录,我们就可以从这个最小值来遍历这个组来找到目标记录。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值