前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >MySQL 核心模块揭秘 | 49 期 | 更新记录的 Undo 日志

MySQL 核心模块揭秘 | 49 期 | 更新记录的 Undo 日志

作者头像
爱可生开源社区
发布2025-01-16 21:58:00
发布2025-01-16 21:58:00
7000
代码可运行
举报
运行总次数:0
代码可运行

作者:操盛春,爱可生技术专家,公众号『一树一溪』作者,专注于研究 MySQL 和 OceanBase 源码。

爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。

本文基于 MySQL 8.0.32 源码,存储引擎为 InnoDB。

目录

  • 1. 准备工作
  • 2. Update Undo 日志格式
  • 3. Update Undo 日志内容
  • 4. Update Undo 日志地址
  • 5. 总结

正文

1. 准备工作

创建测试表:

代码语言:javascript
代码运行次数:0
运行
复制
CREATE TABLE `t6` (
  `id` int unsigned NOT NULL,
  `name` varchar(32) DEFAULT '',
  `mobile` char(11) DEFAULT '',
  `sex` enum('男','女','未填写') DEFAULT NULL,
  `address` varchar(128) DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `idx_name` (`name`),
  KEY `idx_address` (`address`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

插入测试数据:

代码语言:javascript
代码运行次数:0
运行
复制
INSERT INTO `t6` (`id`, `name`, `mobile`, `sex`, `address`) VALUES
(1, '唐僧', '12800128000', '男', '东土大唐'),
(5, '西梁女王', '11800118000', '女', '女儿国'),
(10, '张三', '13800138000', '男', '张家口'),
(15, '李四', '13900139000', '男', '李家庄'),
(20, '王五', '15900159000', '男', '王家大院'),
(25, '紫霞仙子', '19900199000', '女', '九龙城'),
(30, '猪八戒', '16900169000', '男', '高老庄'),
(35, '孙悟空', '17900179000', '男', '花果山'),
(40, '沙和尚', '18900189000', '男', '流沙河');

示例 SQL:

代码语言:javascript
代码运行次数:0
运行
复制
UPDATE `t6`
SET `mobile` = '17988179888', `address` = '水帘洞'
WHERE `id` = 35;

2. Update Undo 日志格式

Update 语句更新表中一条记录,先更新主键索引,再更新二级索引。更新二级索引的流程为:遍历二级索引,每轮循环处理一个二级索引,如果当前二级索引包含 Update 语句的 Set 子句中指定的字段,则更新该二级索引,否则,不更新。

更新主键索引记录之前,会生成 Undo 日志,并写入 Undo 页。更新二级索引记录,不会生成 Undo 日志。更新记录产生的 Undo 日志格式,如下图所示。

各属性详细说明如下:

  • next_record_offset,占用 2 字节,表示下一条 Undo 日志在 Undo 页中的偏移量。
  • type_flag,占用 1 字节,表示这条 Undo 日志的类型,以及一些标志位。
  • lob_flag,占用 1 字节,硬编码为 0x00。
  • undo_no,64 位整数,压缩之后占用 1 ~ 11 字节,表示这条 Undo 日志的编号。
  • table_id,64 位整数,压缩之后占用 1 ~ 11 字节。这个属性值是表 ID,表示事务更新哪个表的记录产生了这条 Undo 日志。
  • info_bits,占用 1 字节,表示 InnoDB 记录的头信息中一些标志位。
  • DB_TRX_ID,压缩之后占用 5 ~ 9 字节,表示本次 Update 操作之前,InnoDB 表中记录的 DB_TRX_ID 字段值。
  • DB_ROLL_PTR,压缩之后占用 5 ~ 9 字节,表示本次 Update 操作之前,InnoDB 表中记录的 DB_ROLL_PTR 字段值。
  • primary_field_len,压缩之后占用 1 ~ 5 字节,表示主键字段值的长度。
  • primary_field_value,占用 primary_field_len 字节,存储时不会压缩。 如果主键是由多个字段组成的联合主键,Undo 日志中,按照联合主键定义的字段顺序,写入所有主键字段的长度和值: primary_field_len_1 primary_field_value_1 primary_field_len_2 primary_field_value_2 ... ... primary_field_len_N primary_field_value_N
  • n_updated,压缩之后占用 1 ~ 5 字节,表示本次 Update 操作更新了几个字段。
  • upd_field_pos,压缩之后占用 1 ~ 5 字节,表示更新字段在表中的位置。
  • upd_field_len,压缩之后占用 1 ~ 5 字节,表示 Update 语句的 Set 子句中指定的字段,更新之前的字段值的长度(占用字节数)。
  • upd_field_value,占用 upd_field_len 字节,表示 Update 语句的 Set 子句中指定的字段,更新之前的字段值,存储时不会压缩。 如果更新了 N 个字段(N >= 2),Undo 日志中,按照 Update 语句的 Set 子句中指定的字段顺序,写入更新字段在表中的位置、更新之前的字段值的长度、更新之前的字段值。如下: upd_field_pos_1 upd_field_len_1 upd_field_value_1 upd_field_pos_2 upd_field_len_2 upd_field_value_2 ... ... upd_field_pos_N upd_field_len_N upd_field_value_N
  • index_field_bytes,占用 2 字节,表示 index_field_bytesindex_field_posindex_field_lenindex_field_value 这个区域占用的总字节数。
  • index_field_pos,压缩之后占用 1 ~ 5 字节,表示二级索引字段在表中的位置。
  • index_field_len,压缩之后占用 1 ~ 5 字节,表示本次 Update 操作之前,二级索引字段值的长度。
  • index_field_value,占用 index_field_len 字节,表示本次 Update 操作之前,二级索引的字段值。 如果表中有 N 个字段(N >= 2)属于二级索引,Undo 日志中,按照二级索引字段在表中出现的位置,写入字段值的长度和字段值。如下: index_field_pos_1 index_field_len_1 index_field_value_1 index_field_pos_2 index_field_len_2 index_field_value_2 ... ... index_field_pos_N index_field_len_N index_field_value_N
  • current_record_offset,这条 Undo 日志在 Undo 页中的偏移量。

type_flag 属性,表示 Undo 日志的类型,还包含一些标志位,如下。

从图中可以看到,type_flag 分为四部分:

  • 第 1 ~ 4 位,对应 offset 0 ~ 3,表示 Undo 日志的类型。
  • 第 5 ~ 6 位,对应 offset 4 ~ 5。 如果 offset 4 的值为 1,表示 Update 操作没有更新主键字段、二级索引字段。 如果 offset 5 的值为 1,表示 Update 操作更新字段时,没有改变字段值的长度,也就是更新之前和更新之后,字段值的长度相同,整条记录的长度自然也就没有改变。
  • 第 7 位,对应 offset 6,如果值为 1,表示这条 Undo 日志中包含 lob_flag 属性。
  • 第 8 位,对应 offset 7,如果值为 1,表示更新了溢出字段。

info_bits 属性,表示本次 Update 操作之前,记录的头信息中第 1 字节的第 5 ~ 8 位(对应 offset 4 ~ 7)的标志位,如下。

  • min_rec_flag,对应 offset 4,如果值为 1,表示该记录是主键索引 B+ 树的非叶子节点中的最小记录。Undo 日志中,info_bits 拷贝自主键索引 B+ 树叶子结点的头信息,此标志为 0。
  • deleted_flag,对应 offset 5,如果值为 1,表示该记录已经被标记删除了,等待事务提交后,后台 purge 线程物理删除该记录。
  • version_flag,对应 offset 6,用于 Online DDL。
  • instant_flag,对应 offset 7,用于 Online DDL。

3. Update Undo 日志内容

示例 SQL 更新 t6 表中 <id = 35> 的记录产生的 Undo 日志,如下图所示。

各属性值详细说明如下:

  • 420,下一条 Undo 日志在 Undo 页中的偏移量。这个值不会压缩,固定占用 2 字节。
  • 76,这个值由 12 | 64 得到,不会压缩,固定占用 1 字节。 12 表示这条 Undo 日志是更新记录产生的,代码里定义为 TRX_UNDO_UPD_EXIST_REC64 表示这条 Undo 日志中包含 lob_flag 属性,代码里定义为 TRX_UNDO_MODIFY_BLOB
  • 0x00,代码里把 lob_flag 的值硬编码为 0x00,也就是 0。
  • 0,这条 Undo 日志的编号。压缩之后占用 1 字节。 这个值来源于事务对象的 undo_no 属性。事务产生的第一条 Undo 日志编号为 0,第二条 Undo 日志编号为 1,依此类推。
  • 1431,这是 t6 表的 ID。压缩之后占用 2 字节。
  • 0,Update 操作之前,t6 表中 <id = 35> 的记录的头信息中第 1 字节第 5 ~ 8 位的值。
  • 2342,Update 操作之前,t6 表中 <id = 35> 的记录中 DB_TRX_ID 字段的值。压缩之后占用 5 字节。
  • 36310272004391275,Update 操作之前,t6 表中 <id = 35> 的记录的 DB_ROLL_PTR 字段的值,压缩之后占用 8 字节。
  • 4,主键字段(id)值的长度。压缩之后占用 1 字节。
  • 35,主键字段(id)值。主键字段类型为 int unsigned,占用 4 字节。
  • 2,更新的字段数量。压缩之后占用 1 字节。如果 Update 语句的 Set 子句中指定的字段值,和表中对应字段的值不同,这个属性值就等于 Set 子句中指定的字段数量。
  • 4,更新的第一个字段(mobile)在表中的位置。
  • 11,Update 操作之前,mobile 字段值的长度。压缩之后占用 1 字节。
  • 17900179000,Update 操作之前的 mobile 字段值。
  • 6,更新的第二个字段(address)在表中的位置。
  • 9,Update 操作之前,address 字段值的长度。压缩之后占用 1 字节。
  • 花果山,Update 操作之前的 address 字段值。
  • 30index_field_bytesindex_field_posindex_field_lenindex_field_value 这个区域(对应图中紫色区域 offset 388 ~ 418)占用的总字节数。
  • 0,二级索引中 id 字段在表中的位置。id 是主键索引字段,同时也是所有二级索引的最后一个字段。
  • 4,Update 操作之前,id 字段值的长度。
  • 35,Update 操作之前的 id 字段值。
  • 3,二级索引 idx_name 中 name 字段在表中的位置。
  • 9,Update 操作之前,name 字段值的长度。
  • 孙语空,Update 操作之前的 name 字段值。
  • 6,二级索引 idx_address 中 address 字段在表中的位置。
  • 9,Update 操作之前,address 字段值的长度。
  • 花果山,Update 操作之前的 address 字段值。
  • 337,这条 Undo 日志在 Undo 页中的偏移量。这个值不会压缩,固定占用 2 字节。

4. Update Undo 日志地址

InnoDB 存储引擎的表中,每条记录都有个隐藏字段 DB_ROLL_PTR,字段长度固定为 7 字节。通过这个字段值可以找到 Undo 日志(也是 MVCC 中记录的历史版本)。

从整体上来看,我们可以认为它是 Undo 日志的地址。但是,这个字段值实际上由 4 部分组成,如下图所示。

各属性详细说明如下:

  • is_insert,表示这条 Undo 日志是否是插入记录产生的。
  • undo_space_id,这条 Undo 日志所属 Undo 表空间的 ID。 InnoDB 最多支持 127 个 Undo 表空间,ID 范围是 0 ~ 127。7 bit 可以表示的最大数字正好是 127。
  • page_no,这条 Undo 日志所属 Undo 页的页号。
  • offset,这条 Undo 日志在 Undo 页中的偏移量。

DB_ROLL_PTR 的计算公式如下:

代码语言:javascript
代码运行次数:0
运行
复制
is_insert << 55 | undo_space_id << 48 | page_no << 16 | offset

以示例 SQL 为例,更新记录时产生 Undo 日志得到的各属性值如下:

  • is_insert = false, 转换成整数就是 0。
  • undo_space_id = 2。
  • page_no = 135。
  • offset = 337。

用 Shell 按照以上公式计算得到 DB_ROLL_PTR,如下:

代码语言:javascript
代码运行次数:0
运行
复制
# 输出结果为 562949962269009
echo $((0 << 55 | 2 << 48 | 135 << 16 | 337))

5. 总结

没有需要总结的内容。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-01-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爱可生开源社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 准备工作
  • 2. Update Undo 日志格式
  • 3. Update Undo 日志内容
  • 4. Update Undo 日志地址
  • 5. 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档