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

在这里简单总结下:

  • 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 列存储一样,没有特殊的:

image

所有字段长度列表(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 页,其结构如下:

image

首先是数据列

所有字段长度列表(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

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

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

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

image

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

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

image

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

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

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

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

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

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!**

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值