1.1.1. 04.InnoDB 数据页结构

「页」是 InnoDB 管理存储空间的基本单位,大小一般是 16KB。页有很多类型,我们先来看看数据页。

数据页结构示意图

名称 中文名 占用空间大小 简单描述
File Header 文件头部 38字节 页的一些通用信息
Page Header 页面头部 56字节 数据页专有的一些信息
Infimum + Supremum 最小记录和最大记录 26字节 两个虚拟的行记录
User Records 用户记录 不确定 实际存储的行记录内容
Free Space 空闲空间 不确定 页中尚未使用的空间
Page Directory 页面目录 不确定 页中的某些记录的相对位置
File Trailer 文件尾部 8字节 校验页是否完整

记录在页中的存储

最开始「页」是没有 User Records 的,当插入一条新数据后,都会从 Free Space 部分申请一个记录大小的空间划分到 User Records 。当 Free Space 被申请完了,那么也就代表这个页使用完了。如果还有新的记录插入,则需要申请新的页。

过程示意图

记录头信息的秘密

先创建一个表

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)

示意图

名称 大小(单位:bit) 描述
预留位1 1 没有使用
预留位2 1 没有使用
delete_mask 1 标记该记录是否被删除
min_rec_mask 1 B+树的每层非叶子节点中的最小记录都会添加该标记
n_owned 4 表示当前记录拥有的记录数
heap_no 13 表示当前记录在记录堆的位置信息
record_type 3 表示当前记录的类型,0表示普通记录,1表示B+树非叶节点记录,2表示最小记录,3表示最大记录
next_record 16 表示下一条记录的相对位置

我们简化一下图: 1

我们插入几条数据再看看:

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

示意图

插入了 4 条记录,但是在 InnoDB 中还自动定义了两个记录分别为最小记录和最大纪录。

1

这两个记录不会放在「页」的 User Records 中,而是单独放在 Infimum + Supremum。

1

在 next_record 中,记录了从当前记录的真实数据到下一个记录的真实数据的地址偏移量。每一行数据串联成一个链表,可以通过一个记录早到它的下一条记录,而且这几个记录是按照主键值由小到大顺序排列。

1

mysql> DELETE FROM page_demo WHERE c1 = 2;
Query OK, 1 row affected (0.02 sec)

当我们删掉第二条记录后,示意图为:

1

我们可以看到变化:

  1. 第二条记录没有从存储空间中移除,而是把 delete_mask 值设置为 1.
  2. 第二条记录的 next_record 变成了 0,意味着没有下一条记录。
  3. 第一条记录的 next_record 指向了第 3 条记录。
  4. 最大记录的 n_owned 值从 5 变成了 4。

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

然后我们再添加上主键值为 2 的记录。

mysql> INSERT INTO page_demo VALUES(2, 200, 'bbbb');
Query OK, 1 row affected (0.00 sec)

示意图

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

Page Directory 页记录

如何查找页中某条记录: 最笨的办法当然是从最小记录开始,沿着表一直查找,总会找到的。

InnoDB 的做法是:

  1. 将所有正常的记录(包括最大和最小记录,不包括被标记为删除的记录 )划分为几个组。
  2. 每个组的最后一条记录(组中最大的那条记录)的头信息中 n_owned 属性标记该记录用友多少条记录,也就是该组共有多少条记录。
  3. 将每个组的最后一条记录的地址偏移量单独提取出来存储到靠近「页」的尾部的地方,也就是 Page Directory ,也就是 页目录。页面目录中的地址偏移量被称之为「槽」(slot),所以这个页面目录就是由「槽」组成。

加入现在 page_demo 表中,共有 6 条记录,InnoDB 会把它们分为两组,第一组只有一个最小记录,第二组是剩余的 5 条记录: 1

注意看最小和最大记录的头信息 n_owned 属性

  • 最小记录的 n_owned 值为 1,表示以最小记录结尾的分组就一条记录,也就是他本身
  • 最大记录的 n_owned 值为 5,表示已最大记录结尾的这个分组有 5 条记录,包括最大记录本身和插入的 4 条记录。

1

InnoDB 规定:

  1. 最小记录所在分组只能有 1 条记录。
  2. 最大纪录所在的分组拥有的记录数只能在 1~8 条之间。
  3. 剩下的分组中记录条数范围只能是 4-8 条。

当我们在插入 12 条记录后,那么一共就有了 18 条记录 1

现在,我们来查找一条记录,通过「二分法」来查找主键值为 6 的记录,最低槽位 low=0,最高的槽 high=4:

  1. 计算中间槽的位置,(0+4)/2=2,所以槽 2 记录的主键值为 8,又因为 8> 6 所以设置 high=2,low 不变。
  2. 重新计算中间槽,(0+2)/2=1,所以槽 1 的主键值为 4,又因为 4<6,所以 low=1,high不变。
  3. 因为 high - low 值为 1,所以确定主键值为 6 的记录在槽 2 中。
  4. 遍历槽 2 中的记录,直接查出来。

所以在一个数据页中查找指定主键值的记录的过程分为两步:

  1. 通过二分法确定该记录所在的槽,并且找到该槽中值最小的那条记录。
  2. 通过记录的 next_record 属性遍历该槽所在组中的各个记录。

Page Header (页面头部)

名称 占用空间大小 描述
PAGE_N_DIR_SLOTS 2字节 在页目录中的槽数量
PAGE_HEAP_TOP 2字节 还未使用的空间最小地址,也就是说从该地址之后就是Free Space
PAGE_N_HEAP 2字节 本页中的记录的数量(包括最小和最大记录以及标记为删除的记录)
PAGE_FREE 2字节 第一个已经标记为删除的记录地址(各个已删除的记录通过next_record也会组成一个单链表,这个单链表中的记录可以被重新利用)
PAGE_GARBAGE 2字节 已删除记录占用的字节数
PAGE_LAST_INSERT 2字节 最后插入记录的位置
PAGE_DIRECTION 2字节 记录插入的方向
PAGE_N_DIRECTION 2字节 一个方向连续插入的记录数量
PAGE_N_RECS 2字节 该页中记录的数量(不包括最小和最大记录以及被标记为删除的记录)
PAGE_MAX_TRX_ID 8字节 修改当前页的最大事务ID,该值仅在二级索引中定义
PAGE_LEVEL 2字节 当前页在B+树中所处的层级
PAGE_INDEX_ID 8字节 索引ID,表示当前页属于哪个索引
PAGE_BTR_SEG_LEAF 10字节 B+树叶子段的头部信息,仅在B+树的Root页定义
PAGE_BTR_SEG_TOP 10字节 B+树非叶子段的头部信息,仅在B+树的Root页定义

File Header (文件头部)

名称 占用空间大小 描述
FIL_PAGE_SPACE_OR_CHKSUM 4字节 页的校验和(checksum值)
FIL_PAGE_OFFSET 4字节 页号
FIL_PAGE_PREV 4字节 上一个页的页号
FIL_PAGE_NEXT 4字节 下一个页的页号
FIL_PAGE_LSN 8字节 页面被最后修改时对应的日志序列位置(英文名是:Log Sequence Number)
FIL_PAGE_TYPE 2字节 该页的类型
FIL_PAGE_FILE_FLUSH_LSN 8字节 仅在系统表空间的一个页中定义,代表文件至少被刷新到了对应的LSN值
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID 4字节 页属于哪个表空间

说明:

  1. FIL_PAGE_SPACE_OR_CHKSUM 校验和,就是通过算法将很长的字节变成一个较短的字节来代表这个字节串,节省比较的时间和空间。
  2. FIL_PAGE_OFFSET 页号,用来定位页。
  3. FIL_PAGE_PREV 和 FIL_PAGE_NEXT,表示页的上一个和下一个页号,让页之间变成双向链表。 1

File Trailer

用来检测页是否完整,因为数据页从内存到磁盘时,可能会出现断电等情况。

Copyright © Kagami丶 2019 all right reserved,powered by Gitbook该文件修订时间: 2019-12-10 21:47:13

results matching ""

    No results matching ""