MySQL原理 - InnoDB引擎 - 行记录存储 - Off-page 列

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: MySQL原理 - InnoDB引擎 - 行记录存储 - Off-page 列
本文基于 MySQL 8

在前面的两篇文章,我们分析了 MySQL InnoDB 引擎的两种行记录存储格式:

在这里简单总结下:

  • Compact 格式结构:
  • 变长字段长度表:包括数据不为NULL的每个可变长度字段的长度,并按照列的顺序逆序排列
  • NULL 值列表:针对可以为 NULL 的字段,用一个 BitMap 来标识哪些字段为 NULL
  • 记录头信息:固定 5 字节,包括:
  • 无用位:2 bits,目前没用
  • deleted_flag:1 bits,标识记录是否被删除
  • min_rec_flag:1 bits,是否是 B+ 树中非叶子节点最小记录标记
  • n_owned:4 bits,记录对应的 slot 中拥有的记录数量
  • heap_no:13 bits,该记录在堆中的序号,也可以理解为在堆中的位置信息
  • record_type:3 bits,记录类型,普通数据记录为000,节点指针类型为 001,伪记录首记录 infimum 行为 010,伪记录最后一个记录 supremum 行为 011,1xx 的为保留的
  • next_record 指针:16 bits,页中下一条记录的相对位置


  • 隐藏列
  • DB_ROW_ID:6 字节,这个列不一定会生成。优先使用用户自定义主键作为主键,如果用户没有定义主键,则选取一个 Unique 键作为主键,如果表中连 Unique 键都没有定义的话,则会为表默认添加一个名为 DB_ROW_ID 的隐藏列作为主键
  • DB_TRX_ID:6 字节,产生当前记录项的事务 id,每开始一个新的事务时,系统版本号会自动递增,而事务开始时刻的系统版本号会作为事务 id,事务 commit 的话,就会更新这里的 DB_TRX_ID
  • DB_ROLL_PTR:7 字节,undo log 指针,指向当前记录项的 undo log,找之前版本的数据需通过此指针。如果事务回滚的话,则从 undo Log 中把原始值读取出来再放到记录中去


  • 数据列
  • bigint:如果不为 NULL,则占用8字节,首位为符号位,剩余位存储数字,数字范围是 -2^63 ~ 2^63 - 1 = -9223372036854775808 ~ 9223372036854775807。如果为 NULL,则不占用任何存储空间
  • double:非 NULL 的列,符合 IEEE 754 floating-point "double format" bit layout 这个统一标准,如果为 NULL,则不占用任何存储空间
  • 对于定长字段,不需要存长度信息直接存储数据即可如果不足设定的长度则补充。例如 char 类型,补充 0x20, 对应的就是空格。
  • varchar 存储:因为数据开头有可变长度字段长度列表,所以 varchar 只需要保存实际的数据即可,不需要填充额外的数据。但是我们还没有考虑存储特别长数据的情况



  • Redundant 格式结构与 Compact 格式的区别:
  • 所有字段长度列表:不同于 Compact 行格式,Redundant 的开头是所有字段长度列表:记录所有字段的长度偏移,包括隐藏列。偏移就是,第一个字段长度为 a,第二个字段长度为 b,那么列表中第一个字段就是 a,第二个字段就是 a + b。所有字段倒序排列
  • 记录头信息:固定 6 字节
  • 无用位:2 bits,目前没用
  • deleted_flag:1 bits,标识记录是否被删除
  • min_rec_flag:1 bits,是否是 B+ 树中非叶子节点最小记录标记
  • n_owned:4 bits,记录对应的 slot 中拥有的记录数量
  • heap_no:13 bits,该记录在堆中的序号,也可以理解为在堆中的位置信息
  • n_field:10 bits,该记录的列数量,范围从1到1023
  • 1byte_offs_flag:1 bit,1 代表每个字段长度的存储为 1 字节,0 代表 2 字节
  • next_record 指针:16 bits,页中下一条记录的相对位置


  • 数据列
  • CHAR 类型存储:无论字段是否为 NULL,或者长度是多少,char(M) 都会占用 M * 字节编码最大长度那么多字节。为 NULL 的话,填充的是 0x00,不为 NULL,长度不够的情况下,末尾补充 0x20.



之前并没有分析当字段比较长的时候会怎么存储,在本篇文章会详细分析。

在此再回顾下之前提到的。因为每条数据都是一个硬盘寻址读取,我们要减少这个硬盘寻址读取的次数,可以考虑一块一块的读取数据,这样,我们很可能下次请求需要的数据就已经在内存中了,就省去了从硬盘读取。基于这个思想,InnoDB 将一个表的数据划分成了若干pages),这些页通过 B-Tree 索引联系起来。每一页大小默认为 16384 Bytes 也就是 16KB(配置为 innodb_page_size)。

对于比较大的字段,例如 Text 类型的字段,如果也存在于这个聚簇索引上,那这个节点数据就会过大,会一下子读取很多页出来,这样读取效率会降低(例如在我们没有想读取这个 Text 列的请求情况下)。所以,InnoDB 对于比较长的变长字段,一般倾向于将他们存储在其他地方,这就涉及到了 Off-page 列的设计模式。不同的 行格式 处理不同。

在开始讨论不同的 行格式 的处理之前,我们先回顾一下 InnoDB 的页大小,InnoDB是一个持久化的存储引擎,也就是数据都是保存在磁盘上面的。但是读写数据,对数据处理,这些是发生在内存中。也就是数据需要从磁盘读取到内存。那么这个读取是如何读取呢?如果处理哪条数据,就读取哪一条到内存中,这样效率也太低了。因为每条数据都是一个硬盘寻址读取,我们要减少这个硬盘寻址读取的次数,可以考虑一块一块的读取数据,这样,我们很可能下次请求需要的数据就已经在内存中了,就省去了从硬盘读取。基于这个思想,InnoDB 将一个表的数据划分成了若干页(pages),这些页通过 B-Tree 索引联系起来。每一页大小默认为 16384 Bytes 也就是 16KB(配置为 innodb_page_size)。在 MySQL 启动的时候可以修改,只能是 4096,8192,16384 其中的一个。


Redundant 中 off-page 列处理


对于 Redundant 行格式中比较长的列,只有前 768 字节会被存储在数据行上,剩下的数据会被放入其他页。我们来看一个实例,运行以下 SQL,创建一个测试表,插入测试数据:

drop table if exists long_column_test;
CREATE TABLE `long_column_test` (
`large_content` varchar(32768) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=REDUNDANT;
##长度为 768 字节
insert into long_column_test values (repeat("az", 384));
##长度为 8100 字节
insert into long_column_test values (repeat("az", 4050));
##长度为 32768 字节
insert into long_column_test values (repeat("az", 16384));

我们使用 64 进制编码器查看表文件 long_column_test.ibd,可以看到第一条数据是一条正常的数据,其存储和之前我们讲的 Redundant 列存储一样,没有特殊的:


微信图片_20220625141406.jpg




所有字段长度列表(8字节,4列,一个数据列,三个隐藏列):03 13(768+7+6+6),00 13(7+6+6),00 0c(6+6), 00 06(6)
记录头(6字节):00 00 10 08 03 ac
隐藏列 DB_ROW_ID(6字节):00 00 00 00 02 22 
隐藏列 DB_TRX_ID(6字节):00 00 00 00 58 b7
隐藏列 DB_ROLL_PTR(7字节):82 00 00 01 0c 01 10 
数据列 large_content(768字节):61 7a ......

对于第二行,我们发现这一行的 large_content 列的数据并没有完全存储在这一行,而是一部分存储在这一行,另一部分存储在了其他地方,这种列就被称为 off-page 列,存储到的其他地方被称为 overflow 页,其结构如下:


微信图片_20220625141429.jpg


首先是数据列

所有字段长度列表(8字节,4列,一个数据列,三个隐藏列):43 27(第一字节的头两位不代表长度,最高位还是标记字段是否为NULL,第二位标记这条记录是否在同一页,由于不为 NULL,所以最高位为 0,由于存在 overflow 页所以不在同一页,所以第二位为1,后面的 3 27 代表长度,即 20+768+7+6+6),00 13(7+6+6),00 0c(6+6), 00 06(6)
记录头(6字节):00 00 10 08 03 ac
隐藏列 DB_ROW_ID(6字节):00 00 00 00 02 22 
隐藏列 DB_TRX_ID(6字节):00 00 00 00 58 b7
隐藏列 DB_ROLL_PTR(7字节):82 00 00 01 0c 01 10 
数据列 large_content(768字节):61 7a ......
指向剩余数据所在地址的指针(20字节):00 00 05 23 00 00 00 05 00 00 00 01 00 00 00 00 00 00 1c a4

对于 off-page 列,列数据末尾会存在指向剩余数据所在地址的指针,这个指针占用 20 字节,它的结构是:



image.png


然后是列剩下的数据存储到的 overflow 页

数据列 large_content(剩余的 7332 字节):61 7a ......

当字段再长一些呢,超过一页内数据的限制的时候呢?我们来看第三行数据结构:


微信图片_20220625141509.jpg


可以看出,过长的数据列,会以链表链接的形式存储在 overflow 页上。

由此可见 Redundant 行格式中,off-page 的结构其实是:


image.png


这样我们会联想到三个问题:

  1. 什么时候列会变成 off-page 列?
  2. 什么时候 overflow 页会分成一个个链表节点存储?
  3. 对于哪些列类型会这么存储?


1. 什么时候列会变成 off-page 列?

首先我们知道一点,innodb 引擎的页大小默认是 16KB,也就是 16384 字节,而且 innodb 的数据是按页加载的。然后,组织 innoDB 引擎数据的数据结构是 B+ 树。扫描 B+ 树寻找数据,也是一页一页加载搜索的。如果一页内能包含的数据行越多,那么很明显,搜索效率越高。但是如果一页中只有一条数据,那么这个 B+ 树其实和链表的效率差不多了。所以,为了效率,需要保证一页内至少有两条数据。所以有:


image.png


同时,一行数据并不是只有列数据,还有隐藏列,记录头,列长度列表等等,并且,innoDB 页也有自己的一些元数据(占用 132 字节,我们在以后的章节会详细分析),在这里我们拿 long_column_test 作为例子,则有:


image.png


可以推导出:


image.png


在实际使用中,可能不止一列数据比较长。还有,由于数据不存储在行数据一起,搜索读取效率会比较低,所以,redundant 行格式会尽可能不把列变为 off-page 列,并尽量少的将列变为 off-page 列。


2. 什么时候 overflow 页会分成一个个链表节点存储?

overflow 页和表数据不同,不通过 B+ 树组织数据,同时不会做复杂搜索,它就是一个链表。所以我们只要保证数据大小不超过一页即可,即:


image.png


这个数据节点也是有一些额外信息的,同时,页也是有自己的额外信息的,这些会在之后的文章中看到。所以,真正承载的数据大小,会需要刨除这些额外信息,也就是小于 16384。如果不够,就会分成多页存储,这些节点会通过一个链表链接起来。


3. 对于哪些列类型会这么存储?

对于可变长度字段,例如 varchar,varbinary,text,blob 等,会利用这种机制存储。对于定长字段,例如 char,如果超长,也会像 varchar 一样存储,在这种情况下,char 末尾就不会填充空白字符了。但是这种情况不常见,char 最长只能 255 个字符,字符编码必须是大于三字节的时候,才会大于 768,例如 uf8mb4 并且每个字符都是大于 3 字节的字符。


Compact 中 off-page 列处理


Compact 中对于 off-page 的处理与 Redundant 基本一样,只是由于数据结构不一样:


image.png


导致列会变成 off-page 列的临界点不一样,在这里我们拿 long_column_test 作为例子,则有:


image.png


可以推导出:



image.png


Dynamic 中 off-page 列处理


Dynamic 除了 off-page 列处理和 Compact 不同以外,其他的基本和 Compact 一样

Dynamic 对于 off-page 列处理的主要区别在于,所有的数据都存储在 overflow 页上面,在 off-page 列只存储 20 字节指针,这个指针的结构和 Redundant 格式中的 20 字节指针一样:


image.png


Compressed 中 off-page 列处理


Compressed 行格式和 Dynamic 基本一致,包括对于 off-page 列处理,其实就是在 Dynamic 的基础上,增加了压缩处理。对于压缩处理,会在后面的压缩页章节详细分析。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
15天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
本文介绍了MySQL InnoDB存储引擎中的数据文件和重做日志文件。数据文件包括`.ibd`和`ibdata`文件,用于存放InnoDB数据和索引。重做日志文件(redo log)确保数据的可靠性和事务的持久性,其大小和路径可由相关参数配置。文章还提供了视频讲解和示例代码。
123 11
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
|
15天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的表空间
InnoDB是MySQL默认的存储引擎,主要由存储结构、内存结构和线程结构组成。其存储结构分为逻辑和物理两部分,逻辑存储结构包括表空间、段、区和页。表空间是InnoDB逻辑结构的最高层,所有数据都存放在其中。默认情况下,InnoDB有一个共享表空间ibdata1,用于存放撤销信息、系统事务信息等。启用参数`innodb_file_per_table`后,每张表的数据可以单独存放在一个表空间内,但撤销信息等仍存放在共享表空间中。
|
15天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的段、区和页
MySQL的InnoDB存储引擎逻辑存储结构与Oracle相似,包括表空间、段、区和页。表空间由段和页组成,段包括数据段、索引段等。区是1MB的连续空间,页是16KB的最小物理存储单位。InnoDB是面向行的存储引擎,每个页最多可存放7992行记录。
|
15天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL的InnoDB存储引擎
InnoDB是MySQL的默认存储引擎,广泛应用于互联网公司。它支持事务、行级锁、外键和高效处理大量数据。InnoDB的主要特性包括解决不可重复读和幻读问题、高并发度、B+树索引等。其存储结构分为逻辑和物理两部分,内存结构类似Oracle的SGA和PGA,线程结构包括主线程、I/O线程和其他辅助线程。
【赵渝强老师】MySQL的InnoDB存储引擎
|
1月前
|
存储 缓存 关系型数据库
详细解析MySQL中的innodb和myisam
总之,InnoDB和MyISAM各有千秋,选择合适的存储引擎应基于对应用程序特性的深入理解,以及对性能、数据完整性和可扩展性的综合考量。随着技术发展,InnoDB因其全面的功能和日益优化的性能,逐渐成为更广泛场景下的首选。然而,在特定条件下,MyISAM依然保留其独特的价值。
129 0
|
3月前
|
监控 关系型数据库 MySQL
在Linux中,mysql的innodb如何定位锁问题?
在Linux中,mysql的innodb如何定位锁问题?
|
3月前
|
SQL 存储 关系型数据库
"MySQL增列必锁表?揭秘InnoDB在线DDL,让你的数据库操作飞一般,性能无忧!"
【8月更文挑战第11天】在数据库领域,MySQL凭借其稳定高效的表现深受开发者喜爱。对于是否会在给数据表添加列时锁表的问题,MySQL的行为受版本、存储引擎等因素影响。从5.6版起,InnoDB支持在线DDL,可在改动表结构时保持表的可访问性,避免长时间锁表。而MyISAM等则需锁表完成操作。例如,在使用InnoDB的表上运行`ALTER TABLE users ADD COLUMN email VARCHAR(255);`时,通常不会完全锁表。虽然在线DDL提高了灵活性,但复杂操作或大表变更仍可能暂时影响性能。因此,进行结构变更前应评估其影响并择机执行。
73 6
|
4月前
|
存储 SQL 关系型数据库
(十三)MySQL引擎篇:半道出家的InnoDB为何能替换官方的MyISAM?
MySQL是一款支持拔插式引擎的数据库,在开发过程中你可以根据业务特性,从支持的诸多引擎中选择一款适合的,例如MyISAM、InnoDB、Merge、Memory(HEAP)、BDB(BerkeleyDB)、Example、Federated、Archive、CSV、Blackhole.....
|
4月前
|
存储 关系型数据库 MySQL
MySQL InnoDB存储引擎的优点有哪些?
上述提到的特性和优势使得InnoDB引擎非常适合那些要求高可靠性、高性能和事务支持的场景。在使用MySQL进行数据管理时,InnoDB通常是优先考虑的存储引擎选项。
178 0
|
4月前
|
负载均衡 关系型数据库 MySQL
MySQL PXC集群多个节点同时大量并发update同一行
如本文标题,MySQL PXC集群多个节点同时大量并发update同一行数据,会怎样? 为此,本人做了一个测试,来验证到底会怎样!
54 0

热门文章

最新文章

下一篇
无影云桌面