
创建测试表:
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;
插入测试数据:
INSERT INTO `t1` (`id`, `i1`) VALUES
(10, 101), (20, 201), (30, 301), (40, 401);
示例 SQL,删除 <id = 40> 的记录:
DELETE FROM t1 WHERE id = 40;
Delete 语句删除表中一条记录,先标记删除主键索引记录,再标记删除二级索引记录。事务提交之后,后台 purge 线程才会把标记删除的记录物理删除,从而最终完成从表中删除一条记录的流程。
事务提交之前,Delete 语句会把记录的头信息中的删除标志位设置为 1,同时更新记录中 DB_TRX_ID、DB_ROLL_PTR 两个隐藏字段的值。这些操作从本质上来说,其实是更新操作。
所以,删除一条记录产生的 Undo 日志的格式和更新一条记录产生的 Undo 日志的格式基本相同,唯一不同之处是删除一条记录产生的 Undo 日志中,没有更新字段区域。
删除主键索引记录之前,会生成 Undo 日志,并写入 Undo 页。删除二级索引记录,不会生成 Undo 日志。删除记录产生的 Undo 日志格式,如下图所示。

各属性详细说明如下:
DB_TRX_ID 字段值。DB_ROLL_PTR 字段值。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_Nindex_field_bytes、index_field_pos、index_field_len、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_Ntype_flag 属性,表示 Undo 日志的类型,还包含一些标志位,如下。

从图中可以看到,type_flag 分为四部分:
info_bits 属性,表示本次 Delete 操作之前,记录的头信息中第 1 字节的第 5 ~ 8 位(对应 offset 4 ~ 7)的标志位,如下。

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

各属性值详细说明如下:
14 | 64 得到,不会压缩,固定占用 1 字节。
14 表示这条 Undo 日志是删除记录产生的,代码里定义为 TRX_UNDO_DEL_MARK_REC。
64 表示这条 Undo 日志中包含 lob_flag 属性,代码里定义为 TRX_UNDO_MODIFY_BLOB。undo_no 属性。事务产生的第一条 Undo 日志编号为 0,第二条 Undo 日志编号为 1,依此类推。<id = 40> 的记录的头信息中第 1 字节第 5 ~ 8 位的值。<id = 40> 的记录中 DB_TRX_ID 字段的值。压缩之后占用 5 字节。<id = 40> 的记录中 DB_ROLL_PTR 字段的值,压缩之后占用 8 字节。int unsigned,占用 4 字节。index_field_bytes、index_field_pos、index_field_len、index_field_value 这个区域(对应图中紫色区域 offset 363 ~ 377)占用的总字节数。int,InnoDB 存储 int 类型字段的值时,会把最高位(第 32 位)设置为 1,用来表示这个字段值是有符号整数。这相当于给字段值加上了 2147483648。
<id = 40> 的记录中 i1 字段的值(401)加上 2147483648,结果就是 2147484049。InnoDB 存储引擎的表中,每条记录都有个隐藏字段 DB_ROLL_PTR,字段长度固定为 7 字节。通过这个字段值可以找到 Undo 日志(也是 MVCC 中记录的历史版本)。
从整体上来看,我们可以认为它是 Undo 日志的地址。但是,这个字段值实际上由 4 部分组成,如下图所示。

各属性详细说明如下:
DB_ROLL_PTR 的计算公式如下:
is_insert << 55 | undo_space_id << 48 | page_no << 16 | offset
以示例 SQL 为例,删除记录时产生 Undo 日志得到的各属性值如下:
用 Shell 按照以上公式计算得到 DB_ROLL_PTR,如下:
# 输出结果为 281474985427281
echo $((0 << 55 | 1 << 48 | 133 << 16 | 337))
删除一条记录产生的 Undo 日志的格式和更新一条记录产生的 Undo 日志的格式基本相同,唯一不同之处是删除一条记录产生的 Undo 日志中,没有更新字段区域。大家对比这两类 Undo 日志格式的示意图,就能很直观的看到两者的区别。