前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MySQL 核心模块揭秘 | 13 期 | 回滚到 savepoint

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

作者头像
爱可生开源社区
发布2024-04-11 15:53:54
1060
发布2024-04-11 15:53:54
举报

正文

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:

代码语言:javascript
复制
/* 1 */ begin;
/* 2 */ insert into t1(id, i1)
        values(50, 501);
/* 3 */ savepoint savept1;
/* 4 */ insert into t1(id, i1)
        values(60, 601);
/* 5 */ savepoint savept2;
/* 6 */ update t1 set i1 = 100
        where id = 10;
/* 7 */ savepoint savept3;
/* 8 */ insert into t1(id, i1)
        values(70, 701);
/* 9 */ rollback to savept2;

每条 SQL 前面的数字是它的编号,9 条 SQL 分别为 SQL 1、SQL 2、...、SQL 9,其中,SQL 9 是本文的主角。

SQL 2 插入记录 <id = 50, i1 = 501> 产生的 undo 日志编号为 0。

SQL 4 插入记录 <id = 60, i1 = 601> 产生的 undo 日志编号为 1。

SQL 6 更新记录 <id = 10> 产生的 undo 日志编号为 2。

SQL 8 插入记录 <id = 70, 701> 产生的 undo 日志编号为 3。

2. 查找 savepoint

每个用户线程都有一个 m_savepoints 链表,用户每创建一个 savepoint,它的对象都会追加到链表末尾。

每个 savepoint 对象的 prev 属性都指向在它之前创建的那个 savepoint 对象,多个 savepoint 对象通过 prev 属性连接成链表。

m_savepoints 链表的指针,指向最新加入的 savepoint 对象。

示例 SQL 中,SQL 3、5、7 分别创建了 savept1savept2savept3,这 3 个 savepoint 对象形成的 m_savepoints 链表如下:

要回滚到某个 savepoint,第 1 步就是根据名字找到它,因为它里面保存着两个重要数据:

  • 创建 savepoint 时的 binlog offset。
  • 创建 savepoint 之前,最后产生的一条 undo 日志的编号。

m_savepoints 链表的指针指向最新加入的 savepoint 对象,查找过程自然就是从后往前了。

从后往前遍历 m_savepoints 链表的过程中,如果当前遍历的 savepoint 对象名字等于要回滚的那个 savepoint 对象名字,就找到了,否则,继续往前遍历。

如果遍历完 m_savepoints 链表都没有找到目标 savepoint 对象,就会报错:

代码语言:javascript
复制
(1305, 'SAVEPOINT xxx does not exist')

3. binlog 回滚

回滚到某个 savepoint 的过程中,binlog 回滚就是把创建该 savepoint 之后执行 SQL 产生的 binlog 日志都丢弃。

事务提交之前,产生的 binlog 日志都临时存放于 trx cache,而 trx cache 包含内存 buffer 和磁盘临时文件两部分。

trx cache 中的 binlog 日志,可能有一部分在内存 buffer 中,另一部分在磁盘临时文件中。

基于此,丢弃 binlog 日志可以分为两种情况:

情况 1:丢弃内存 buffer 中的部分或全部 binlog 日志。

这种情况比较简单,不涉及到磁盘临时文件。

trx cache 用 IO_CACHE 来管理内存 buffer 和磁盘临时文件。

IO_CACHE 有个 write_pos 属性,这是个指针,指向新产生的 binlog 日志要写入内存 buffer 中的哪个位置。

binlog 回滚,只需要把 write_pos 往回移动,write_pos 新位置和旧位置之间的那些 binlog 日志就被丢弃了。

那么,write_pos 要往回移动到哪个位置呢?

IO_CACHE 还有个 pos_in_file 属性,这是个整数值,我们也可以把它看成指针,指向内存 buffer 写满之后,里面的内容转移到磁盘临时文件中的哪个位置。

savepoint 中保存着它创建的那一时刻的 binlog offset,binlog offset 减去 pos_in_file 就是 write_pos 要往回移动到的位置。

情况 2:丢弃内存 buffer 中的全部 binlog 日志,同时还要丢弃磁盘临时文件中的部分或全部 binlog 日志。

这种情况要分两步走:

  • 把 write_pos 移动到内存 buffer 的开始处,丢弃内存 buffer 中的所有 binlog 日志。
  • 把 pos_in_file 移动到 savepoint 中保存的 binlog offset 处,丢弃磁盘临时文件中 binlog offset 之后的所有 binlog 日志。

回滚之前,各指针位置如下图所示:

回滚之后,各指针位置如下图所示:

SQL 9 回滚到 savept2 的过程中,binlog 回滚只需要丢弃内存 buffer 中的部分 binlog 日志,也就是对应情况 1

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 日志是插入记录 <id = 60, i1 = 601> 产生的,编号为 2。

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

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

  • 读取最新的 undo 日志(编号为 4)。
  • 解析 undo 日志得到 <id = 70>
  • 删除 t1 表中 id = 70 的记录。
  • 读取上一条 undo 日志(编号为 3)。
  • 解析 undo 日志得到 <id = 10, i1 = 101>,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 期 | 回滚整个事务。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 准备工作
  • 2. 查找 savepoint
  • 3. binlog 回滚
  • 4. InnoDB 回滚
  • 5. 删除 savepoint
  • 6. 总结
相关产品与服务
云数据库 MySQL
腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档