MySQL 核心模块揭秘 | 13 期 | 回滚到 savepoint

目录

  • 1. 准备工作

  • 2. 查找 savepoint

  • 3. binlog 回滚

  • 4. InnoDB 回滚

  • 5. 删除 savepoint

  • 6. 总结

正文

1. 准备工作

创建测试表:

CREATE TABLE `t1` (<br>  `id` int unsigned NOT NULL AUTO_INCREMENT,<br>  `i1` int DEFAULT '0',<br>  PRIMARY KEY (`id`) USING BTREE,<br>  KEY `idx_i1` (`i1`)<br>) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;<br>

4. InnoDB 回滚

事务执行过程中,改变(插入、更新、删除)表中的每条数据,都会对应产生一条 undo 日志。

回滚到某个 savepoint 的过程中,InnoDB 回滚,就是按照 undo 日志产生的时间,从后往前读取 undo 日志。

每读取一条 undo 日志之后,解析 undo 日志,然后执行产生这条日志的操作的反向操作,也就是回滚。

如果某条 undo 日志是插入操作产生的,反向操作就是删除。

如果某条 undo 日志是更新操作产生的,更新操作把字段 A 的值从 101 改为 100,反向操作就是把字段 A 的值从 100 改回 101。

如果某条 undo 日志是删除操作产生的,反向操作就是把记录再插回到表里。

那么,回滚到哪条 undo 日志才算完事呢?

savepoint 中,保存着它创建之前,最后产生的那条 undo 日志的编号,回滚到这条 undo 日志的下一条 undo 日志就完事了。

以 SQL 5 为例,创建 savept2 之前,最后一条 undo 日志是插入记录 产生的,编号为 2。

SQL 9,rollback to savept2
回滚到编号为 2 的 undo 日志的下一条 undo 日志(编号为 3)就完事了。

SQL 9 需要回滚编号为 3、4 的两条 undo 日志。以回滚主键索引记录为例,过程如下:

  • 读取最新的 undo 日志(编号为 4)。
  • 解析 undo 日志得到
  • 删除 t1 表中 id = 70 的记录。
  • 读取上一条 undo 日志(编号为 3)。
  • 解析 undo 日志得到
    ,101 是 id = 10 的记录被修改之前的 i1 字段值。
  • 把 t1 表中 id = 10 的记录的 i1 字段值,从 100 改回 101。
  • 读取上一条 undo 日志,日志编号为 2,等于 savept2 中保存的 undo 日志编号,不需要回滚这条 undo 日志,InnoDB 回滚操作结束。

5. 删除 savepoint

执行完 binlog 和 InnoDB 的回滚操作之后,还需要删除该 savepoint 之后创建的其它 savepoint。

示例 SQL 中,SQL 5 创建 savept2 之后,SQL 7 又创建了 savept3。

SQL 9 回滚到 savept2,执行完 binlog 和 InnoDB 的回滚操作之后,savept3 就没用了,会被删除。

删除 savept3 之后,m_savepoints 链表如下图所示:

6. 总结

回滚到某个 savepoint,首先要从 m_savepoints 链表中找到这个 savepoint。

找到之后,根据其中保存的 binlog offset、undo 日志编号,执行 binlog 和 InnoDB 的回滚操作。

binlog 回滚就是丢弃 binlog offset 之后的 binlog 日志。

InnoDB 回滚就是根据产生时间,从后往前读取并解析 undo 日志,执行 undo 日志对应的回滚操作。

最后,就是删除在这个 savepoint 之后创建的其它 savepoint。

本期问题:关于本期内容,如有问题,欢迎留言交流。

下期预告:MySQL 核心模块揭秘 | 14 期 | 回滚整个事务。