MySQL的数据页结构和记录的管理

摘要

MySQL 的页有多种类型,本文主要讲述数据页的结构和记录管理,数据页也就是表中一行一行的数据组成的页。

前言

MySQL 的数据存储在磁盘中,每次执行 SQL 命令时需要把对应的记录从磁盘读取到内存中,这个过程叫做磁盘 IO。无论磁盘使用传统的机械硬盘,还是使用固态硬盘,磁盘 IO 相对于内存 IO 来说,速度都是较慢的。以机械硬盘为例,每进行一次随机 IO,都需要磁头移动到对应的磁道大约需要 10 ms,这个过程称为寻道,然后磁盘旋转到读取数据的位置,这个过程耗时与磁盘的转速有关,一般为 3 ms,但是内存的一次随机读取大约耗时在几百纳秒,比磁盘随机 IO 快了好几个数量级。

为了减少磁盘 IO 的次数,MySQL 将若干记录组成一个数据页,一般一个数据页的大小为 16 KB,每次磁盘 IO 最少读取一个数据页,很多时候还会把相邻的数据页一起读到内存,这种 IO 称为连续 IO,相对于随机 IO,省了寻道的时间,这个时间占磁盘 IO 的大头。

MySQL 有多种存储引擎,不同的存储引擎对页和行的结构设计有所不同,本文只讲述 InnoDB 使用的页和行。在 InnoDB 存储引擎中,页有多种用途,不同用途的页结构也会有所不同,下文主要讲述数据页的结构,其它类型的页结构比较相似,就不再展开了,感兴趣的可以自行了解。

1. 数据页的结构

数据页占用的16KB空间可以划分为多个部分,具体如下图所示。

页结构示意图

  • File Header:文件头部,主要存储页的通用信息。
  • Page Header:页面头部,数据页独有的信息。
  • Infimum+Supremum:页面中最小记录和最大记录。
  • User Record:用户记录,即表中的数据行。
  • Free Space:页的空闲空间。
  • Page Directory:页目录,页中某些记录的相对位置。
  • File Trailer:文件尾部,校验页是否完整。

2. 记录在页中的存储

我们知道记录存储在页的User Record部分,具体存储形式如下图所示,
记录在页中的存储形式

2.1 记录的存储结构

记录的格式一般称为行格式,如果做过主从复制,应该对这个概念比较清楚。行格式有 4 种,分别为COMPACTREDUNDANTDYNAMICCOMPRESSED。行格式不同,记录在磁盘中的存储结构就不同,下文主要讲述COMPACT格式,其它行格式类似,不再展开。

COMPACT格式组成如下图所示,
COMPACT行结构

2.1.1 变长字段列表

表中有些字段是可变长度的,例如varchar(20)类型,其中的 20 表示这个字段能够存储的最大字符数,变长字段列表就是记录变长字段实际使用的字节数。如果要将字符数转换为字节数需要确定表使用的字符集,例如 ascii 字符集,一个字符占用一个字节,如果是 utf8mb4 字符集,一个字符占用 4 个字节。

2.1.2 NULL值列表

表中有些字段是允许为空的,每个允许为空的列在 NULL 值列表中都会对应一个比特位,比特位为 1 时,说明对应的列存储的是 NULL;比特位为 0 时,说明对应的列存储的值非空。这样区分的好处是,值为 NULL 的列不需要分配存储空间,能够节省内存。

2.1.3 记录头

如上图所示,记录头包含如下结构:

名称大小(比特)描述
保留位2暂未使用
deleted_flag1删除标记,为 1 表示该记录已被删除
min_rec_flag1讲索引时再介绍
n_owned4一个页中的记录会分为多个组,组长的这个字段显示该分组包含的记录数
heap_no13表示当前记录是user_record区域(也称为堆)的第几条记录,即记录的编号
record_type3记录类型,0:普通记录,1:B+树非叶子节点记录,2:Infimum 记录,3:Supremum 记录
next_record16下一条记录在页面的偏移量

2.1.4隐藏列

每条记录都有三个隐藏列,

  • row_id:占用 6 个字节。记录的唯一标识。该列只是确保每条记录有一个唯一标识,如果表中设置了主键或非空的唯一索引,那么该列不会生成。
  • trx_id:占用 6 个字节。表示事物 ID。讲 MVCC 时再做详细介绍。
  • roll_pointer:占用 7 个字节。表示回滚指针。讲 MVCC 时再做详细介绍。

这三个隐藏列都是 InnoDB 自动生成的,用户不需要去维护。

2.2 记录的管理方式

通过行格式中的heap_nonext_record两个字段可以知道记录在页中以链表的方式存在,具体如下图所示(仅展示必须的字段),记录的链表结构
在这里插入图片描述
如图所示,有两条虚拟的记录 Infimum 和 Supremum 分别在链表的头部和尾部,表示该页中存储的最小记录和最大记录,这两条虚拟记录的heap_no固定为 0 和 1。链表中的其它记录也是按照由小到大的顺序排列的。

记录包含多个字段,如何比较记录的大小?
假设表中只有一个主键没有其它索引,那么就是通过比较主键的大小来比较记录的大小。如果没有主键还能比较隐藏列row_id的大小。

3. Page Directory(页目录)

如果存在如下SQL,其中id为主键,

select * from t where id = 7;

按照常规思路,我们需要遍历页中的所有记录,才能找到满足id = 7的记录,这样查询的效率太低了。这时可能有人会说,链表中的记录是有序的,为什么不能使用二分查找呢?虽然我们知道链表的最小值和最大值,但是不知道中间值,这是由链表的特性决定的,不能快速定位到某条记录。然而,如果能把链表转成数组,就能愉快的使用二分查找了。

InnoDB 将链表的记录进行分组,每组最大的记录的n_owned记录了组内的记录数量。每个组内最大记录的地址偏移量保存到页目录中,这些偏移量称为,每个槽占用两个字节。具体组织形式如下图所示,
页目录分组原理图

槽 0 只有一条记录,因为 InnoDB 规定了 Infimum 所在的分组只能有一条记录,即 Infimum 记录独自成组。Supremum 记录所在的分组可以有 1-8 条记录,其余分组可以有 4-8 条记录。多个槽指向的记录构成了一个有序数组,当插入一条新记录时,只需要使用二分搜索找到应该插入的槽,然后遍历槽中的记录,即可找到插入的位置。相对于遍历整页的记录,这种查询方式的效率要高很多。

4. Page Header(页面头部)

页面头部主要记录了整个页的统计信息。主要包含如下内容,

  • PAGE_N_DIR_SLOTS:占用 2 字节,表示页目录中槽的数量。
  • PAGE_HEAP_TOP:占用 2 字节,表示页中Free Space区域的首地址。
  • PAGE_N_HEAP:占用 2 字节,表示第 1 位本页的记录是否为紧凑型记录,剩余 15 位表示本页的记录条数。
  • PAGE_FREE:占用 2 字节,每个被标记删除的记录会组成一个链表,PAGE_FREE 记录该链表的头节点的偏移量。
  • PAGE_GARBAGE:占用 2 字节,已删除记录占用的字节数。
  • PAGE_LAST_INSERT:占用 2 字节,最后插入记录的位置。
  • PAGE_DIRECTION:占用 2 字节,记录插入的方向。新纪录插入到页中,会有两个方向,如果比上次插入的记录大,则算是右边插入;反之,则是左边插入。
  • PAGE_N_DIRECTION:占用 2 字节,一个方向连续插入的记录数量。反应了最近一系列插入记录的单调性。
  • PAGE_N_RECS:占用 2 字节,本页用户创建的记录数量。
  • PAGE_MAX_TRX_ID:与事务相关,后面再讲。
  • PAGE_LEVEL:与索引相关,后面再讲。
  • PAGE_INDEX_ID:与索引相关,后面再讲。
  • PAGE_BTR_SEG_LEAF:与索引相关,后面再讲。
  • PAGE_BTR_SEG_TOP:与索引相关,后面再讲。

5. File Header(文件头部)和 File Ttrailer(文件尾部)

不同类型的页,其结构也不相同,但都有文件头部和尾部。头部主要记录各种页的信息,以及页与页之间的关系。主要有如下内容,省略不用的字段,

  • FIL_PAGE_OFFSET:占用 4 字节,表示页号。
  • FIL_PAGE_PREV:占用 4 字节,表示上一页的页号。
  • FIL_PAGE_NEXT:占用 4 字节,表示下一页的页号。
  • FIL_PAGE_LSN:MVCC 相关,后面再讲。
  • FIL_PAGE_TYPE:占用 2 字节,表示该页的类型。
  • FIL_PAGE_FILE_FLUSH_LSN:MVCC 相关,后面再讲。
  • FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID:页属于的表空间。

通过FIL_PAGE_PREVFIL_PAGE_NEXT通过这两个字段,可以看出页与页之间也组成链表结构,已知页内的记录是有序的,可以猜测若干页组成的链表也是有序的,这是实现索引的基础。

文件尾部主要用于页的完整性校验。

思考题

一个页的大小为 16 KB,里面包含了固定头信息和记录信息。如果记录中某个字段占用的字节数超过了 16 KB,那么 InnoDB 会如何处理这条记录呢?

参考文献

  1. 磁盘原理简要分析
  2. MySQL是怎样运行的
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MySQL是一个关系型数据管理系统,它使用表来存储和组织数据。每个表由一系列行和列组成,每行表示一个记录,每列表示记录的属性。 在MySQL中,数据结构主要包括以下几个方面: 1. 数据库(Database):MySQL中的数据数据库的形式进行组织和管理。一个数据库可以包含多个表。 2. 表(Table):表是MySQL中最基本的数据组织单位。每个表由若干行和列组成,行表示记录,列表示记录的属性。 3. 列(Column):表中的每个字段被称为列。列定义了字段的名称和数据类型,例如整数、字符、日期等。 4. 行(Row):表中的每条记录被称为行。每行包含了一条完整的数据记录,其中每个字段对应一列。 5. 主键(Primary Key):主键是一列或一组列,用于唯一标识表中的每条记录。主键保证了表中的记录唯一性,并且可以用于建立表之间的关系。 6. 外键(Foreign Key):外键是一列或一组列,用于建立表与表之间的关系。外键引用了其他表的主键,从而实现了表之间的关联。 7. 索引(Index):索引是对表中一列或多列的值进行排序的数据结构。索引可以提高查询的速度,加快数据的检索。 8. 视图(View):视图是一个虚拟表,它是基于一个或多个表的查询结果。视图可以简化复杂的查询操作,并提供了数据的安全性和灵活性。 以上是MySQL中常用的数据结构,它们相互配合使用,可以有效地组织和管理数据

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值