分布式事务之——MySQL对XA事务的支持

PHP   Mysql  

MySQL 从5.0.3开始支持XA分布式事务,且只有InnoDB存储引擎支持。MySQL Connector/J 从5.0.0版本之后开始直接提供对XA的支持。

需要注意的是, 在DTP模型中,mysql属于资源管理器(RM)。而一个完整的分布式事务中,一般会存在多个RM,由事务管理器TM来统一进行协调。因此,这里所说的mysql对XA分布式事务的支持,一般指的是单台mysql实例如何执行自己的事务分支。

MySQL XA 事务SQL语法MySQL XA 事务SQL语法
https://dev.mysql.com/doc/refman/5.7/en/xa-statements.html

  1. XA {START|BEGIN} xid [JOIN|RESUME] //开启XA事务,如果使用的是XA START而不是XA BEGIN,那么不支持[JOIN|RESUME],xid是一个唯一值,表示事务分支标识符
  2. XA END xid [SUSPEND [FOR MIGRATE]] //结束一个XA事务,不支持[SUSPEND [FOR MIGRATE]]
  3. XA PREPARE xid 准备提交
  4. XA COMMIT xid [ONE PHASE] //提交,如果使用了ONE PHASE,则表示使用一阶段提交。两阶段提交协议中,如果只有一个RM参与,那么可以优化为一阶段提交
  5. XA ROLLBACK xid //回滚
  6. XA RECOVER [CONVERT XID] //列出所有处于PREPARE阶段的XA事务

下面是一个简单的msyql XA事务案例,演示了mysql作为全局事务中的一个事务分支,将一行记录插入到一个表中。

  1. mysql> XA START 'xatest’; //其中'xatest’就是xid的值
  2. Query OK, 0 rows affected (0.00 sec)
  3. mysql> insert into user(name) values("tianshozuhi");
  4. Query OK, 1 row affected (0.00 sec)
  5. mysql> XA END 'xatest';
  6. Query OK, 0 rows affected (0.00 sec)
  7. mysql> XA PREPARE 'xatest';
  8. Query OK, 0 rows affected (0.01 sec)
  9. mysql> XA COMMIT 'xatest';
  10. Query OK, 0 rows affected (0.01 sec)

Mysql XA事务状态
https://dev.mysql.com/doc/refman/5.7/en/xa-states.html

XA事务的状态,按照如下步骤进行展开

  1. 使用XA START来启动一个XA事务,并把它置于ACTIVE状态。
  2. 对于一个ACTIVE状态的 XA事务,我们可以执行构成事务的SQL语句,然后发布一个XA END语句。XA END把事务放入IDLE状态。
  3. 对于一个IDLE 状态XA事务,可以执行一个XA PREPARE语句或一个XA COMMIT…ONE PHASE语句:
    XA PREPARE把事务放入PREPARED状态。
    在此点上的XA RECOVER语句将在其输出中包括事务的xid值,因为XA RECOVER会列出处于PREPARED状态的所有XA事务。
    XA COMMIT…ONE PHASE用于预备和提交事务。xid值将不会被XA RECOVER列出,因为事务终止。
  4. 对于一个PREPARED状态的 XA事务,您可以发布一个XA COMMIT语句来提交和终止事务,或者发布XA ROLLBACK来回滚并终止事务。
    针对一个给定的客户端连接而言,XA事务和非XA事务(即本地事务)是互斥的。例如,已经执行了”XA START”命令来开启一个XA事务,则本地事务不会被启动,直到XA事务已经被提交或被 回滚为止。相反的,如果已经使用START TRANSACTION启动一个本地事务,则XA语句不能被使用,直到该事务被提交或被 回滚为止。

最后,如果一个XA事务处于ACTIVE状态,是不能直接进行提交的,如果这样做,mysql会抛出异常:

  1. ERROR 1399 (XAE07): XAER_RMFAIL: The command cannot be executed
  2. when global transaction is in the ACTIVE state

关于XID的说明
mysql中使用xid来作为一个事务分支的标识符。事实上xid作为事务分支标识符是在XA规范中定义的,在<< Distributed Transaction Processing: The XA Specification>> 4.2 节中,规定了一个xid的结构,通过C语言进行描述,如下:

  1. /∗
  2. Transaction branch identification: XID and NULLXID:
  3. ∗/
  4. #define XIDDATASIZE 128 /∗ size in bytes ∗/
  5. #define MAXGTRIDSIZE 64 /∗ maximum size in bytes of gtrid ∗/
  6. #define MAXBQUALSIZE 64 /∗ maximum size in bytes of bqual ∗/
  7. struct xid_t {
  8. long formatID; /* format identifier */
  9. long gtrid_length; /* value 1-64 */
  10. long bqual_length; /* value 1-64 */
  11. char data[XIDDATASIZE];
  12. };
  13. /∗
  14. A value of -1 in formatID means that the XID is null.
  15. ∗/
  16. typedef struct xid_t XID;
  17. /∗
  18. Declarations of routines by which RMs call TMs:
  19. ∗/
  20. extern int ax_reg(int, XID ∗, long);
  21. extern int ax_unreg(int, long);

XA规范定义了一个xid有4个部分组成:

gtrid:
全局事务标识符(global transaction identifier),最大不能超过64字节

bqual:
分支限定符(branch qualifier),最大不能超过64字节

data:
xid的值,其是 gtrid和bqual拼接后的内容。因为gtrid和bqual最大都是64个字节,因此data的最大长度为128。不过,在xid的结构体中,并没有gtrid和bqual,只有gtrid_length、bqual_length。由于二者的内容都存储在data中,因此我们可以根据data反推出gtrid和bqual。举例来说,假设gtrid为”g12345”(5个字节),bqual为”b456”(4个字节)。那么在构造xid结构体时,gtrid_length=5,bqual_length=4,data=”g12345b456”,那么在反推的时候:
从data[0]到data[gtrid_length-1]之间的部分就是gtrid的值;从data[gtrid_length]到data[gtrid_length+bqual_length-1]部分就是bqual的值。

formatId:
而formatId的作用就是记录gtrid、bqual的格式,类似于memcached中flags字段的作用。XA规范中通过一个结构体约定了xid的组成部分,但是并没有规定data中存储的gtrid、bqual内容到底应该是什么格式。你可以选择使用数字,也可以选择使用字符串,到底选择什么由开发者自行决定,只要最终能保证data中的内容是全局唯一的即可。XA规范建议使用OSI CCR风格来组织xid的内容,此时formatId应该设置为0.

在mysql官方文档中,关于xid的组成也有类似的说明:
xid: gtrid [, bqual [, formatID ]]

其中,bqual、formatID是可选的。解释如下:

gtrid : 是一个全局事务标识符(global transaction identifier),

bqual:是一个分支限定符(branch qualifier),如果没有提供bqual,那么默认值为空字符串’’。

formatID:是一个数字,用于标记gtrid和bqual值的格式,这是一个无符号整数(unsigned integer),也就是说,最小为0。如果没有提供formatID,那么其默认值为1。

特别需要注意的是,xid作为一个事务分支的标识符,理论上只要有分支限定符(bqual)就可以了,为什么要包含全局事务标识符(gtrid)?这主要是为了管理方便,通过包含进xid,我们可以很容易的判断出这个事务分支属于哪一个全局事务。

例如,前面提到 XA RECOVER命令的作用是列出所有处于PREPARE阶段的XA事务,以下是一个案例:

  1. mysql> XA RECOVER;
  2. +----------+--------------+--------------+--------------+
  3. | formatID | gtrid_length | bqual_length | data |
  4. +----------+--------------+--------------+--------------+
  5. | 1 | 6 | 6 | g12345b67890 |
  6. +----------+--------------+--------------+--------------+

这里列出的是一个分支事务xid的组成信息,根据前面的介绍,我们可以推断出:

gtrid是data[0]到data[gtrid_length-1]部分的内容,即data[0]到data[6-1=5]部分的内容,结果为g12345;

而bqual是data[gtrid_length]到data[gtrid_length+bqual_length-1]部分的内容,即data[6]到data[6+6-1=11]部分的内容,结果b67890。

因此,根据这个信息,我们就可以判断出这个xid表示的是:全局事务(g12345)中的事务分支(b67890)。

PHP使用XA例子

  1. <?php
  2. class connDb{
  3. private static $host = '127.0.0.1:3306';
  4. private static $username = 'root';
  5. private static $password = 'root';
  6. private static $dbName = 'test';
  7. private $conn = null;
  8. public function __construct(){
  9. $this->conn = new MySQLi(self::$host,self::$username,self::$password,self::$dbName);
  10. if(!$this->conn){
  11. die('数据库连接错误:'.$this->conn->connect_error);
  12. }else{
  13. echo '连接成功';
  14. }
  15. $this->conn->query("set names utf-8");
  16. }
  17. public function execute_xa($sql){
  18. $this->conn->query($sql);
  19. }
  20. public function execute_dml($sql){
  21. $rs = $this->conn->query($sql);
  22. if(!$rs){
  23. $flag = 0;
  24. die('数据库操作出错:'.$this->conn->error);
  25. }else if($this->conn->affected_rows > 0){
  26. $flag = 1;
  27. }else{
  28. $flag = 2;
  29. }
  30. return $flag;
  31. }
  32. public function closeConn(){
  33. $this->conn->close();
  34. }
  35. }
  36. testAction();
  37. function testAction(){
  38. $XA = uniqid("");
  39. $conn1 = new connDb();
  40. $conn2 = new connDb();
  41. $sql1 = "update account set money = money - 100 where name = 'czz'";
  42. $sql2 = "update account set money = money + 100 where name = 'cmq'";
  43. $conn1->execute_xa("XA START '$XA'");
  44. $conn2->execute_xa("XA START '$XA'");
  45. try{
  46. $add_status = $conn1->execute_dml($sql1);
  47. $del_status = $conn1->execute_dml($sql2);
  48. $conn1->execute_xa("XA END '$XA'");
  49. $conn2->execute_xa("XA END '$XA'");
  50. $conn1->execute_xa("XA PREPARE '$XA'");
  51. $conn2->execute_xa("XA PREPARE '$XA'");
  52. if($add_status != 1 || $del_status != 1){
  53. throw new Exception("操作失败");
  54. }
  55. echo '程序运行成功!';
  56. $conn1->execute_xa("XA COMMIT '$XA'");
  57. $conn2->execute_xa("XA COMMIT '$XA'");
  58. }catch (Exception $ex){
  59. echo '程序运行失败!';
  60. $conn1->execute_xa("XA ROLLBACK '$XA'");
  61. $conn2->execute_xa("XA ROLLBACK '$XA'");
  62. }
  63. }

文章相关链接:
https://blog.csdn.net/l1028386804/article/details/79769043
https://blog.csdn.net/Csoap2/article/details/89359127



评论 0

发表评论

Top