作者:操盛春,爱可生技术专家,公众号『一树一溪』作者,专注于研究 MySQL 和 OceanBase 源码。
爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。
本文基于 MySQL 8.0.32 源码,存储引擎为 InnoDB。
正文
创建测试表:
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);
删除 <id = 40> 的记录:
DELETE FROM t1 WHERE id = 40;
回滚:
ROLLBACK;
回滚 Delete 操作过程中读取 Undo 日志,和回滚 Insert 操作过程中读取 Undo 日志的流程一样,这里不再赘述。
准备工作中,Delete 语句删除 <id = 40> 的记录产生的 Undo 日志如下:
删除记录产生的 Undo 日志的格式,除了没有更新字段区域
,剩余的其它部分和更新记录产生的 Undo 日志的格式相同。删除记录产生的 Undo 日志的参数区域
,和插入记录产生的 Undo 日志的参数区域的不同之处,可以参考前面关于回滚更新记录的内容。
删除记录产生的 Undo 日志的参数区域,对应图中 offset [339, 344)
:
14 | 64
得到。
14 在代码里定义为 TRX_UNDO_DEL_MARK_REC
,表示这条 Undo 日志由删除记录产生,参数区域之后的其它属性按照 TRX_UNDO_DEL_MARK_REC
类型的 Undo 日志格式解析。
64 在代码里定义为 TRX_UNDO_MODIFY_BLOB
,有这个标志就意味着 Undo 日志的参数区域包含 lob_flag
属性。TRX_UNDO_MODIFY_BLOB
标志,所以 Undo 日志中写入了 lob_flag 属性。这个属性值硬编码为 0x00,实际上没有使用。头信息和隐藏字段区域,对应图中 offset [344, 358)
,包含 3 个属性:
解析出来之后,这 3 个属性的值会保存到回滚操作内存对象(undo_node
)的 update
属性中。
回滚时,这 3 个属性的值分别被拷贝到 <id = 40> 的记录的头信息、DB_TRX_ID 和 DB_ROLL_PTR 字段中。
主键字段区域,对应图中 offset [358, 363)
,包含 2 个属性:
解析出来之后,主键字段值保存到回滚操作内存对象(undo_node
)的 ref
属性中。
二级索引字段区域,对应图中 offset [363, 377)
,存放的是 <id = 40> 的记录对应的所有二级索引记录的字段值。
因为每个二级索引记录的末尾都包含主键字段,所以,Undo 日志的这个区域中记录了 id 字段的信息。每组 <index_field_pos, index_field_len, index_field_value>
对应一个字段的信息。
这个区域存放的二级索引字段信息,回滚时不需要解析,因为用不到。purge 线程清理标记删除的二级索引记录时才会用到。
前面从 Undo 日志中解析主键字段值(id)得到 40
,保存到了回滚操作内存对象(undo_node
)的 ref
属性中。现在需要根据主键字段值去主键索引的 B+ 树中查找 <id = 40>
的记录。
找到记录之后,读取记录中所有字段值,保存到回滚操作内存对象(undo_node
)的 row
属性中。另外,还会保存指向主键索引 B+ 树中 <id = 40>
的记录的指针,后面回滚这条主键索引记录时会用到。
undo_node 对象的 row 属性中,既包含我们创建表时指定的字段,也包含 InnoDB 自己加上的隐藏字段 DB_TRX_ID
、DB_ROLL_PTR
。因为我们创建表时指定了主键,记录中不会包含隐藏字段 DB_ROW_ID
。row 属性中保存的各字段值如下:
以上这些是 t1 表中 <id = 40> 的记录被当前回滚事务标记删除
之后的各字段值。其中 DB_TRX_ID 是当前回滚事务的 ID,DB_ROLL_PTR 是当前 Undo 日志的地址。
另外,<id = 40> 的记录的头信息中第 1 字节第 5 ~ 8 位也发生了变化,因为这条记录已经被当前回滚事务标记删除,标记删除对应的标志位会被设置为 1。
Delete 操作删除 t1 表中 <id = 40> 的记录,会先删除主键索引记录,然后遍历所有二级索引,每一轮遍历删除一个二级索引中的对应主键索引中 <id = 40> 的二级索引记录。回滚时,则会按照相反的顺序来,先逐个回滚二级索引记录,最后才回滚主键索引记录。
t1 表中只有一个二级索引 idx_i1,回滚流程如下:
undo_node
)的 row
属性中保存的 i1 和 id 字段值,构造二级索引记录 <i1 = 401, id = 40>。前面构造主键索引记录时,已经找到了主键索引中 <id = 40> 的记录,也保存了指向主键索引 B+ 叶子结点中 <id = 40> 的记录的指针。回滚主键索引记录时,可以直接使用这个指针操作 <id = 40> 的记录。
用回滚操作内存对象(undo_node
)的 update
属性中保存的 <id = 40> 的记录的信息,回滚主键索引记录的流程如下:
DB_TRX_ID
、DB_ROLL_PTR
两个字段的值,把 <id = 40> 的记录恢复到被当前回滚事务标记删除之前的状态。回滚 Delete 操作产生的一条 Undo 日志的主要流程如下: