首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >MySQL 核心模块揭秘 | 52 期 | 删除记录的 Undo 日志

MySQL 核心模块揭秘 | 52 期 | 删除记录的 Undo 日志

作者头像
爱可生开源社区
发布2025-02-12 14:42:27
发布2025-02-12 14:42:27
3660
举报
正文

1. 准备工作

创建测试表:

代码语言:javascript
复制
CREATE TABLE `t1` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `i1` int DEFAULT '0',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_i1` (`i1`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

插入测试数据:

代码语言:javascript
复制
INSERT INTO `t1` (`id`, `i1`) VALUES 
(10, 101), (20, 201), (30, 301), (40, 401);

示例 SQL,删除 <id = 40> 的记录:

代码语言:javascript
复制
DELETE FROM t1 WHERE id = 40;

2. Delete Undo 日志格式

Delete 语句删除表中一条记录,先标记删除主键索引记录,再标记删除二级索引记录。事务提交之后,后台 purge 线程才会把标记删除的记录物理删除,从而最终完成从表中删除一条记录的流程。

事务提交之前,Delete 语句会把记录的头信息中的删除标志位设置为 1,同时更新记录中 DB_TRX_IDDB_ROLL_PTR 两个隐藏字段的值。这些操作从本质上来说,其实是更新操作。

所以,删除一条记录产生的 Undo 日志的格式和更新一条记录产生的 Undo 日志的格式基本相同,唯一不同之处是删除一条记录产生的 Undo 日志中,没有更新字段区域。

删除主键索引记录之前,会生成 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 字节,表示本次 Delete 操作之前,InnoDB 表中记录的 DB_TRX_ID 字段值。
  • DB_ROLL_PTR,压缩之后占用 5 ~ 9 字节,表示本次 Delete 操作之前,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
  • index_field_bytes,占用 2 字节,表示 index_field_bytesindex_field_posindex_field_lenindex_field_value 这个区域占用的总字节数。
  • index_field_pos,压缩之后占用 1 ~ 5 字节,表示二级索引字段在表中的位置。
  • index_field_len,压缩之后占用 1 ~ 5 字节,表示本次 Delete 操作之前,二级索引字段值的长度。
  • index_field_value,占用 index_field_len 字节,表示本次 Delete 操作之前,二级索引的字段值。 如果表中有 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。只有 Update 操作会使用这两个位,Delete 操作不会使用。
  • 第 7 位,对应 offset 6,如果值为 1,表示这条 Undo 日志中包含 lob_flag 属性。
  • 第 8 位,对应 offset 7。只有 Update 操作会使用这个位,Delete 操作不会使用。

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

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

3. Delete Undo 日志内容

示例 SQL 删除 t1 表中 <id = 40> 的记录产生的 Undo 日志,如下图所示。

各属性值详细说明如下:

  • 420,下一条 Undo 日志在 Undo 页中的偏移量。这个值不会压缩,固定占用 2 字节。
  • 78,这个值由 14 | 64 得到,不会压缩,固定占用 1 字节。 14 表示这条 Undo 日志是删除记录产生的,代码里定义为 TRX_UNDO_DEL_MARK_REC64 表示这条 Undo 日志中包含 lob_flag 属性,代码里定义为 TRX_UNDO_MODIFY_BLOB
  • 0x00,代码里把 lob_flag 的值硬编码为 0x00,也就是 0。
  • 0,这条 Undo 日志的编号。压缩之后占用 1 字节。 这个值来源于事务对象的 undo_no 属性。事务产生的第一条 Undo 日志编号为 0,第二条 Undo 日志编号为 1,依此类推。
  • 1067,这是 t1 表的 ID。压缩之后占用 2 字节。
  • 0,Delete 操作之前,t1 表中 <id = 40> 的记录的头信息中第 1 字节第 5 ~ 8 位的值。
  • 2846,Delete 操作之前,t1 表中 <id = 40> 的记录中 DB_TRX_ID 字段的值。压缩之后占用 5 字节。
  • 36310272004456759,Delete 操作之前,t1 表中 <id = 40> 的记录中 DB_ROLL_PTR 字段的值,压缩之后占用 8 字节。
  • 4,主键字段(id)值的长度。压缩之后占用 1 字节。
  • 40,主键字段(id)值。主键字段类型为 int unsigned,占用 4 字节。
  • 14index_field_bytesindex_field_posindex_field_lenindex_field_value 这个区域(对应图中紫色区域 offset 363 ~ 377)占用的总字节数。
  • 0,二级索引中 id 字段在表中的位置。id 是主键索引字段,同时也是所有二级索引的最后一个字段。
  • 4,Delete 操作之前,id 字段值的长度。
  • 40,Delete 操作之前的 id 字段值。
  • 3,二级索引 idx_i1 中 i1 字段在表中的位置。
  • 4,Delete 操作之前,i1 字段值的长度。
  • 2147484049,Delete 操作之前的 i1 字段值。 i1 字段的类型为 int,InnoDB 存储 int 类型字段的值时,会把最高位(第 32 位)设置为 1,用来表示这个字段值是有符号整数。这相当于给字段值加上了 2147483648。 <id = 40> 的记录中 i1 字段的值(401)加上 2147483648,结果就是 2147484049。
  • 337,这条 Undo 日志在 Undo 页中的偏移量。这个值不会压缩,固定占用 2 字节。

4. Delete 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
复制
is_insert << 55 | undo_space_id << 48 | page_no << 16 | offset

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

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

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

代码语言:javascript
复制
# 输出结果为 281474985427281
echo $((0 << 55 | 1 << 48 | 133 << 16 | 337))

5. 总结

删除一条记录产生的 Undo 日志的格式和更新一条记录产生的 Undo 日志的格式基本相同,唯一不同之处是删除一条记录产生的 Undo 日志中,没有更新字段区域。大家对比这两类 Undo 日志格式的示意图,就能很直观的看到两者的区别。

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

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

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

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

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