聊聊数据库中的 savepoint

本文涉及的产品
云原生数据库 PolarDB 分布式版,标准版 2核8GB
简介: 故事要从全局二级索引开始讲起。 当我们构建了一个全局二级索引之后,一条逻辑上的数据插入,就会变成两条物理上的数据插入:一条插入到主表,另一条插入到索引表。为了保证主表和索引表数据的一致性,我们往往需要开启分布式事务,再并行地插入两条数据。如果其中一条数据插入失败了,比如索引上出现了唯一键冲突,但主表的数据已经插了进去,怎么办呢?

作者:勿遮  本文来源:PolarDB-X知乎号

从全局二级索引讲起

故事要从全局二级索引开始讲起。 当我们构建了一个全局二级索引之后,一条逻辑上的数据插入,就会变成两条物理上的数据插入:一条插入到主表,另一条插入到索引表。为了保证主表和索引表数据的一致性,我们往往需要开启分布式事务,再并行地插入两条数据。如果其中一条数据插入失败了,比如索引上出现了唯一键冲突,但主表的数据已经插了进去,怎么办呢?当然,我们可以简单粗暴地回滚整个事务,来保证数据的一致性。 但有的时候,我们已经在事务里执行了大量的操作,这时候仅仅因为一条数据的插入失败,就要回滚整个事务,代价实在太大。对于单机 MySQL 来说,如果出现了这种插入 UK 报唯一键冲突的情况,会自动回滚这条插入的语句。至于是忽略报错继续执行事务,还是回滚整个事务,则交给业务方来决定。作为一款全面兼容 MySQL 的分布式数据库,PolarDB-X 自然也要具备这种特性。 其实,不只是全局二级索引的情况,其他场景比如 batch insert/delete/update、广播表 DML 等都可能会遇到这种情况。

聊聊 savepoint

如果要回滚单条或多条语句,而非回滚整个事务,我们自然想到使用 savepoint 这一功能。在事务中,我们可以随时设置一个 savepoint,后续再回滚到这个 savepoint,从而回滚 savepoint 后的所有操作。 MySQL 是如何实现 savepoint 能力的呢? MySQL 在 server 层中,对每个事务对象维护了一个 savepoint 的链表,用于记录这个事务设置过的 savepoint 对象。其中,每个 savepoint 对象主要记录了 savepoint 的名字,用于标识不同的 savepoint 对象。 在设置一个 savepoint 时,会往链表末尾插入一个 savepoint 对象。在释放一个 savepoint 时,会根据 savepoint 名字遍历链表,找到对应的 savepoint 对象,将其及其后面的所有 savepoint 删除。在回滚一个 savepoint 时,会找到对应的 savepoint 对象,根据其存储的信息进行回滚操作,随后,还会隐式释放掉其后的所有 savepoint(不包括它自己)。 可以看到,每个 savepoint 对象都需要存储一定的信息,来告诉 binlog 和 innodb 需要回滚到什么位置。对于 binlog 记录的是设置 savepoint 时的 binlog cache 的 offset;对于 innodb,则是设置 savepoint 时 undo log 的 undo number。这两个简单的信息,就足够 binlog 和 innodb 完成回滚操作了。 事实上,innodb 内部还维护了事务的 savepoint 链表,但本质上和上述说的链表没什么太大差异,就不展开讨论了。

使用 savepoint 解决问题

那 PolarDB-X 该如何使用 DN 的 savepoint 解决一开始提到的全局二级索引的问题呢? 其实做法也很简单,我们只需要在任何物理语句执行之前,加上一个 savepoint,在所有物理语句执行之后,视情况来回滚或是释放 savepoint。我们将这一行为称为 auto-savepoint。 其实,innodb 的行为也是如此,其在每条语句前(实际是上一条语句执行后),会更新一个匿名的 savepoint 对象 last_sql_stat_start,其保存了上一条语句执行后的 undo number。在当前语句执行出错时,通过这个 undo number 来回滚掉这条语句的操作。 熟悉 PolarDB-X 的同学一定知道,PolarDB-X 通过物理连接(计算节点到存储节点的连接)来执行物理 SQL。对于一条逻辑更新 GSI 的 SQL 语句,可能需要使用 2 条物理连接,执行 3 条物理 SQL(一条主表 update,一条 GSI 表删除,一条 GSI 表插入)。如下所示:

物理连接 0(物理分库 0): 
update primary_tb; insert gsi_tb; 
物理连接 1(物理分库 1): 
delete gsi_tb;

设置 auto-savepoint 的关键就在于要在合适的时机设置 savepoint。在这个例子中,任何一个物理连接执行出错,都会通知其他连接中断其正在执行的操作。假设在物理连接 1 执行 delete gsi_tb 的时候报错了,我们不知道物理连接 0 上的具体执行情况。哪些语句执行成功了、哪些语句执行失败了、哪些语句还没开始执行,我们都不知道。此时,我们可以借助 savepoint 的能力,不管具体的执行情况如何,都统一回滚到一切操作还没开始做的状态,就能达到回滚单条逻辑 SQL 的效果。 因此,我们自动设置的 savepoint 行为就是:

物理连接 0(物理分库 0): 
savepoint `s0`; update primary_tb; insert gsi_tb; rollback to savepoint `s0`;
物理连接 1(物理分库 1): 
savepoint `s0`; delete gsi_tb (ERROR); rollback to savepoint `s0`;

当然,这里面的设计还会保证参与了一条逻辑 SQL 的所有物理连接都正确设置上 savepoint,以保证 savepoint 的设置和回滚都不会漏掉,否则就会出现数据不一致的问题了。

代价是什么

我们通过 DN 的 savepoint 能力,来实现 CN 层面上的回滚单条语句的功能。尽管从前面的讨论来看,设置和释放 savepoint 的代价都比较低,只是在链表上新增或删除一个元素,但我们还是需要在实现上尽量减轻这种代价。 首先,我们尽量避免 savepoint 的设置,只在涉及 GSI 或其他逻辑执行的 DML 时,才自动设置 savepoint。因为只有在逻辑执行下,才可能发生分片间不一致的场景,才需要 auto-savepoint 来保证逻辑语句的原子性。其次,我们设置和释放都是通过多语句的方式,将 savepoint 的 SQL 和业务产生的物理 SQL 一并下发,避免增加额外的 RTT。最后,我们还使用了私有协议绕过 savepoint SQL 的解析过程,直接在 DN 上调用设置和释放 savepoint 的代码。


云原生数据库PolarDB分布式版新增标准版形态,基于X-Paxos提供100%兼容MySQL的高可靠性集中式数据库服务。

阿里巴巴集团双十一同款数据库,即刻拥有

相关实践学习
快速体验PolarDB开源数据库
本实验环境已内置PostgreSQL数据库以及PolarDB开源数据库:PolarDB PostgreSQL版和PolarDB分布式版,支持一键拉起使用,方便各位开发者学习使用。
相关文章
|
21天前
|
SQL 关系型数据库 MySQL
【揭秘】MySQL binlog日志与GTID:如何让数据库备份恢复变得轻松简单?
【8月更文挑战第22天】MySQL的binlog日志记录数据变更,用于恢复、复制和点恢复;GTID为每笔事务分配唯一ID,简化复制和恢复流程。开启binlog和GTID后,可通过`mysqldump`进行逻辑备份,包含binlog位置信息,或用`xtrabackup`做物理备份。恢复时,使用`mysql`命令执行备份文件,或通过`innobackupex`恢复物理备份。GTID模式下的主从复制配置更简便。
91 2
|
16天前
|
弹性计算 关系型数据库 数据库
手把手带你从自建 MySQL 迁移到云数据库,一步就能脱胎换骨
阿里云瑶池数据库来开课啦!自建数据库迁移至云数据库 RDS原来只要一步操作就能搞定!点击阅读原文完成实验就可获得一本日历哦~
|
19天前
|
关系型数据库 MySQL 数据库
RDS MySQL灾备服务协同解决方案构建问题之数据库备份数据的云上云下迁移如何解决
RDS MySQL灾备服务协同解决方案构建问题之数据库备份数据的云上云下迁移如何解决
|
16天前
|
人工智能 小程序 关系型数据库
【MySQL】黑悟空都掌握的技能,数据库隔离级别全攻略
本文以热门游戏《黑神话:悟空》为契机,深入浅出地解析了数据库事务的四种隔离级别:读未提交、读已提交、可重复读和串行化。通过具体示例,展示了不同隔离级别下的事务行为差异及可能遇到的问题,如脏读、不可重复读和幻读等。此外,还介绍了在MySQL中设置隔离级别的方法,包括全局和会话级别的调整,并通过实操演示了各隔离级别下的具体效果。本文旨在帮助开发者更好地理解和运用事务隔离级别,以提升数据库应用的一致性和性能。
95 2
【MySQL】黑悟空都掌握的技能,数据库隔离级别全攻略
|
22天前
|
数据可视化 关系型数据库 MySQL
Mysql8 如何在 Window11系统下完成跳过密钥校验、完成数据库密码的修改?
这篇文章介绍了如何在Windows 11系统下跳过MySQL 8的密钥校验,并通过命令行修改root用户的密码。
Mysql8 如何在 Window11系统下完成跳过密钥校验、完成数据库密码的修改?
|
19天前
|
SQL 关系型数据库 MySQL
【MySQL 慢查询秘籍】慢SQL无处遁形!实战指南:一步步教你揪出数据库性能杀手!
【8月更文挑战第24天】本文以教程形式深入探讨了MySQL慢SQL查询的分析与优化方法。首先介绍了如何配置MySQL以记录执行时间过长的SQL语句。接着,利用内置工具`mysqlslowlog`及第三方工具`pt-query-digest`对慢查询日志进行了详细分析。通过一个具体示例展示了可能导致性能瓶颈的查询,并提出了相应的优化策略,包括添加索引、缩小查询范围、使用`EXPLAIN`分析执行计划等。掌握这些技巧对于提升MySQL数据库性能具有重要意义。
50 1
|
20天前
|
关系型数据库 MySQL Linux
在Linux中,如何配置数据库服务器(如MySQL或PostgreSQL)?
在Linux中,如何配置数据库服务器(如MySQL或PostgreSQL)?
|
24天前
|
SQL 存储 关系型数据库
数据库-MySQL-01(一)
数据库-MySQL-01(一)
17 4
|
11天前
|
前端开发 C# 设计模式
“深度剖析WPF开发中的设计模式应用:以MVVM为核心,手把手教你重构代码结构,实现软件工程的最佳实践与高效协作”
【8月更文挑战第31天】设计模式是在软件工程中解决常见问题的成熟方案。在WPF开发中,合理应用如MVC、MVVM及工厂模式等能显著提升代码质量和可维护性。本文通过具体案例,详细解析了这些模式的实际应用,特别是MVVM模式如何通过分离UI逻辑与业务逻辑,实现视图与模型的松耦合,从而优化代码结构并提高开发效率。通过示例代码展示了从模型定义、视图模型管理到视图展示的全过程,帮助读者更好地理解并应用这些模式。
27 0
|
11天前
|
SQL 数据采集 关系型数据库