聊聊数据库中的 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分布式版,支持一键拉起使用,方便各位开发者学习使用。
相关文章
|
7月前
|
存储 数据库 OceanBase
想问一下OceanBase数据库这个的日志都能删除么?clog
是的,OceanBase数据库企业版确实支持performance_schema。performance_schema是一个集合了数据库服务器性能参数信息的数据字典,它能够帮助我们收集和存储数据库服务器的性能参数信息,以便于我们对数据库进行更好的优化和管理。
188 5
|
SQL 存储 关系型数据库
day01:数据库DDL
day01:数据库DDL
80 0
|
6月前
|
SQL 存储 数据库
sql2005恢复数据库
在SQL Server 2005中恢复数据库涉及多个步骤:登录到SSMS,右键点击“数据库”选择“任务”→“还原”→“数据库”;指定备份源,选择需要的.bak文件;确认还原选项,如覆盖现有数据库;开始还原,等待完成;验证数据的完整性和准确性;如有必要,处理实例编号问题。记得先备份,确保足够磁盘空间,并在遇到问题时参考官方文档。
|
7月前
|
SQL 关系型数据库 MySQL
11. 数据库的事务
11. 数据库的事务
|
SQL 关系型数据库 MySQL
聊聊数据库中的 savepoint
故事要从全局二级索引开始讲起。 当我们构建了一个全局二级索引之后,一条逻辑上的数据插入,就会变成两条物理上的数据插入:一条插入到主表,另一条插入到索引表。为了保证主表和索引表数据的一致性,我们往往需要开启分布式事务,再并行地插入两条数据。如果其中一条数据插入失败了,比如索引上出现了唯一键冲突,但主表的数据已经插了进去,怎么办呢?
|
SQL Oracle 关系型数据库
|
缓存 数据库
数据库三种事务
数据库三种事务
|
SQL 缓存 关系型数据库
14_ 数据库 _ 事务
14_ 数据库 _ 事务
130 0
|
SQL Oracle 关系型数据库
数据库 事务
数据库 事务
112 0
|
SQL 存储 关系型数据库
一、DDL 数据库
一、DDL 数据库
460 0