MySQL分布式事务XA介绍
在生产环境的MySQL架构部署中,为了降低单点压力,通常会根据业务的特征情况,对数据进行分库分表,将数据拆分存储在不同的数据库实例中。由于多节点的存在,原来的本地事务已经不能支持此时的分布式应用场景。MySQL为我们提供了分布式事务解决方案-XA事务。
XA事务支持当前仅限于InnoDB存储引擎。XA支持分布式事务,即允许多个单独的事务资源参与全局事务的能力。事务资源通常是RDBMS,但可能是其他类型的资源。全局事务涉及多个事务本身的操作,但所有操作必须作为一个整体单元成功完成,或者全部作为一个整体单元回滚。
使用全局事务的应用程序涉及一个或多个资源管理器和事务管理器:
-
资源管理器(RM,resource manager)提供对事务资源的访问。数据库服务器是一种资源管理器。必须可以提交或回滚RM管理的事务。
-
事务管理器(TM,transaction manager)事务管理器是分布式事务的核心管理者。它与处理每个事务的RM进行通信,协调并完成事务的处理。全局事务中的各个事务是全局事务的“分支”。事务的各个分支由唯一命名进行标识(XA事务标识符xid,参见:https://dev.mysql.com/doc/refman/5.7/en/xa-statements.html)。
MySQL的XA实现使MySQL服务器可以充当资源管理器,处理全局事务中的XA事务,而连接到MySQL服务器的客户端程序充当事务管理器。要执行全局事务,必须知道涉及哪些组成部分,并将每个组成部分置于可以提交或回滚的位置。而且要么所有都必须提交,要么所有都必须回滚。
XA分布式事务原理:2PC(2阶段提交)
分布式事务通常采用2PC协议,全称Two Phase Commitment Protocol。该协议主要为了解决在分布式数据库场景下,所有节点间数据一致性的问题。分布式事务通过2PC协议将提交分成两个阶段:
-
prepare
-
commit/rollback
在第一阶段,所有分支都准备好了。也就是说,TM告诉他们准备提交。通常,这意味着管理分支的每个RM都会记录稳定存储中分支的操作。分支表明他们是否能够这样做,并且这些结果用于第二阶段。
在第二阶段,TM告诉RM是否提交或回滚。如果所有分支在准备好时都表明他们能够提交,则告知所有分支都要提交。如果任何分支在准备时指示它将无法提交,则告知所有分支回滚。
如下图所示:
图片来源于网络
MySQL的XA事务分为外部XA和内部XA
-
外部XA用于跨多个MySQL实例的分布式事务,需要应用层作为协调者,通俗的说就是比如我们在PHP中写代码,那么PHP书写的逻辑就是协调者。应用层负责决定提交还是回滚,崩溃时的悬挂事务。MySQL数据库外部XA可以用在分布式数据库代理层,实现对MySQL数据库的分布式事务支持,例如开源的代理工具:网易的DDB,淘宝的TDDL等等。
-
内部XA事务用于同一实例下跨多引擎事务,由Binlog作为协调者,比如在一个存储引擎提交时,需要将提交信息写入二进制日志,这就是一个分布式内部XA事务,只不过二进制日志的参与者是MySQL本身。Binlog作为内部XA的协调者,在binlog中出现的内部xid,在crash recover时,由binlog负责提交。(这是因为,binlog不进行prepare,只进行commit,因此在binlog中出现的内部xid,一定能够保证其在底层各存储引擎中已经完成prepare)。
对于“外部XA”,MySQL服务器充当资源管理器,客户端程序充当事务管理器。对于“内部XA”,MySQL服务器中的存储引擎充当RM,而服务器本身充当TM。针对宕机后提交悬挂事务没有记录binlog继而导致主从数据不一致问题,可以将PREPARE成功的事务写日志,但并不是写在二进制日志中,而是写在每个对应分布式事务的单独文件中。
XA事务的基本语法
XA {START|BEGIN} xid [JOIN|RESUME]XA END xid [SUSPEND [FOR MIGRATE]]XA PREPARE xidXA COMMIT xid [ONE PHASE]XA ROLLBACK xidXA RECOVER [CONVERT XID]
每个XA语句都以XA关键字开头,其中大多数都需要一个xid值。xid是XA事务标识符,它指示该语句适用于哪个事务。xid值由客户端提供,或由MySQL服务器生成。更多xid的组成介绍,可以参考文末【XA事务SQL语法】
XA事务示例
由于目前MySQL只有InnoDB存储引擎支持XA事务,所以:
-
首先要确保你选择的引擎是InnoDB
-
查看MySQL是否开启XA事务支持:
SHOW VARIABLES LIKE '%xa%';
如果innodb_support_xa的值是ON,则说明已经开启了XA事务支持,否则需执行:
SET innodb_support_xa = ON
完成上面的验证后,对内部XA和外部XA分别举例如下:
-
内部XA简单示例
mysql> XA START 'xatest';Query OK, 0 rows affected (0.00 sec)mysql> INSERT INTO mytable (i) VALUES(10);Query OK, 1 row affected (0.04 sec)mysql> XA END 'xatest';Query OK, 0 rows affected (0.00 sec)mysql> XA PREPARE 'xatest';Query OK, 0 rows affected (0.00 sec)mysql> XA COMMIT 'xatest';Query OK, 0 rows affected (0.00 sec)
外部XA示例
<?php$xatest1 = new mysqli("192.168.33.10","root","123456","xatest1")or die("xatest1 连接失败");$xatest2 = new mysqli("192.168.33.11","root","123456","xatest2")or die("xatest2 连接失败");//为XA事务指定一个id,xid 必须是一个唯一值。$xid = uniqid();//两个库指定同一个事务id,表明这两个库的操作处于同一事务中$xatest1->query("XA START '$xid'");//开始xa事务$xatest2->query("XA START '$xid'");//开始xa事务try { //$xatest1 $return = $xatest1->query("UPDATE db1 SET class=1 WHERE id=1") ; if($return == false) { throw new Exception("192.168.33.10执行更新失败"); } //$xatest2 $return = $dbtest2->query("UPDATE db2 SET grade=2 WHERE id=1") ; if($return == false) { throw new Exception("192.168.33.11执行更新失败"); } //阶段1:$xatest1提交准备就绪,PREPARE $dbtest1->query("XA END '$xid'"); $dbtest1->query("XA PREPARE '$xid'"); //阶段1:$xatest2提交准备就绪,PREPARE $dbtest2->query("XA END '$xid'"); $dbtest2->query("XA PREPARE '$xid'"); //阶段2:提交两个库 $dbtest1->query("XA COMMIT '$xid'"); $dbtest2->query("XA COMMIT '$xid'");} catch (Exception $e) { //阶段2:回滚 $dbtest1->query("XA ROLLBACK '$xid'"); $dbtest2->query("XA ROLLBACK '$xid'"); echo $e->getMessage();}$xatest1->close();$xatest2->close();
XA事务在使用中的问题
-
在给定客户端连接的上下文中,XA事务和本地(非XA)事务是互斥的。例如,如果XA START已发出已开始XA事务,则在提交或回滚XA事务之前无法启动本地事务。相反,如果已启动本地事务 START TRANSACTION,则在提交或回滚事务之前,不能使用任何XA语句。
-
MySQL数据库的主备数据库的同步,通过Binlog的复制完成。而Binlog是MySQL数据库内部XA事务的协调者,并且MySQL数据库为binlog做了优化——binlog不写prepare日志,只写commit日志。 所有的参与节点prepare完成,在进行xa commit前crash。crash recover如果选择commit此事务。由于binlog在prepare阶段未写,因此主库中看来,此分布式事务最终提交了,但是此事务的操作并未写到binlog中,因此也就未能成功复制到备库,从而导致主备库数据不一致的情况出现。 而crash recover如果选rollback, 那么就会出现全局不一致(该分布式事务对应的节点,部分已经提交,无法回滚,而部分节点回滚。最终导致同一分布式事务,在各参与节点,最终状态不一致)。 针对这个问题,可选的替代方案有(1)不使用主从复制进行备份,而是直接使用xa事务实现同步写来作为备份;(2)将PREPARE成功的事务写日志,但并不是写在二进制日志中,而是写在每个对应分布式事务的单独文件中。
XA事务的性能
XA事务的性能很差,建议采用高性能的消息中间件处理分布式系统间的数据交互,以达到数据最终一致性。
参考资料
-
mysql的XA官方文档:http://dev.mysql.com/doc/refman/5.7/en/xa.html
-
XA事务SQL语法:https://dev.mysql.com/doc/refman/5.7/en/xa-statements.html
-
分布式TP:XA规范 : https://publications.opengroup.org/c193
-
XA事务的限制:https://dev.mysql.com/doc/mysql-reslimits-excerpt/8.0/en/xa-restrictions.html
·END·
青春有限·艺无止境