Mysql分布式事务

x33g5p2x  于2022-06-16 转载在 Mysql  
字(5.5k)|赞(0)|评价(0)|浏览(642)

XA协议

为了规范分布式事务的管理,X/OPEN 提出了分布式事务处理规范XA协议,XA规范了TM与RM之间的通信接口,在TM与多个RM之间形成一个双向通信桥梁,从而在多个数据库资源下保证ACID四个特性。目前知名的数据库,如Oracle, DB2,mysql等,都是实现了XA接口的,都可以作为RM。

XA是数据库的分布式事务,强一致性,在整个过程中,数据都处于被锁住的状态,即从prepare到commit、rollback的整个过程中,TM一直拥有参与分布式事务RM对应的数据库的锁,如果有其他人要修改数据库的该条数据,就必须等待锁的释放,存在长事务风险。

分布式事务模型

X/Open定义了分布式事务处理模型,包括应用程序AP、事务管理器TM、资源管理器RM、通信资源管理器CRM。

在XA规范中分布式事务有AP、RM、TM组成:

  • 应用程序(Application Program):定义事务边界(定义事务开始和结束)并访问事务边界内的资源
  • 资源管理器(Resource Manager):RM管理计算机共享的资源,资源包含比如数据库、文件系统等
  • 事务管理器(Transaction Manager,简称TM):负责管理全局事务,分配事务唯一标识,监控事务的执行进度,并负责事务的提交、回滚、失败恢复等。

流程

分布式事务的基本流程如下:

具体流程如下:

  1. 配置TM,将RM注册到TM
  2. AP从TM获取资源管理器的代理,获取TM所管理的RM的JDBC连接
  3. AP向TM发起全局事务
  4. TM将XID通知到各RM
  5. AP通过Connection连接直接对RM进行操作
  6. AP结束全局事务
  7. TM会通知RM全局事务结束
  8. 开始二阶段提交

两阶段提交

当每个RM都结束了全局事务的执行后,此时每个RM管理的分布式事务分支还没有提交,只是把该事务管理的业务逻辑执行完了。

进入二阶段提交阶段,在这个阶段,会先进入prepare阶段,然后再是commit或者rollback阶段。

具体流程如图:

第一阶段分为两个步骤:

  • 事务管理器通知参与该事务的各个资源管理器,通知他们开启事务、执行SQL(暂不提交),并进入prepare状态(该状态下可执行commit/ rollback)。
  • 资源管理器接收到消息后开始准备阶段,写好事务日志并执行事务,但不提交,然后将是否就绪的消息返回给事务管理器
  • RM根据自己的情况,如果判断自己进行的工作可以被提交,那就就对工作内容进行持久化,并给TM回执OK;否者给TM的回执NO
  • RM在发送了否定答复并回滚了已经的工作后,就可以丢弃这个事务分支信息了

第二阶段也分为两个步骤:

  • 事务管理器在接受各个消息后,开始分析,如果有任意其一失败,则发送回滚命令,否则发送提交命令。
  • 各个资源管理器接收到命令后,执行(耗时很少),并将提交消息返回给事务管理器。

两阶段提交的好处是有了事务管理器进行统一管理,让事务在提交前尽可能的完成所有能完成的工作。同时两阶段提交可以保证事务的一致性,不管是事务管理器还是各个资源管理器,每执行一步操作都会被日志记录,为出现故障后的恢复提供依据。

Mysql中的XA语法

Mysql中分布式操作的基本模板如下:

开启xa事务,XA start <xid>
DML语句,即SQL增删改查语句
终止XA事务,XA end <xid>
预提交事务, XA prepare <xid>,这一步是有返回值的
提交,XA commit <xid>,根据prepare操作的返回结果做的处理
回滚,XA rollback <xid>,根据prepare操作的返回结果做的处理
XA RECOVER: 返回当前数据库中处于PREPARE状态的分支事务的详细信息

每个事务必须有一个唯一的xid值,因此当前值不能被其他XA事务使用,xid是一个XA事务标识符,用来唯一标识一个分布式事务。

xid值可以由客户端提供,或者由Mysql服务器生成。

xid基本格式如下:

xid: gtrid [,bqual] [, formatID]
  • gtrid 是一个分布式事务标识符,相同的分布式事务使用相同的gtrid,这样可以明确知道XA事务属于哪一个分布式事务
  • bqual是一个分支限定符,默认为空串,对于一个分布式事务中的每个分支事务,bqual必须是唯一的
  • formatID是一个数字,用于标识由gtrid和bqual值使用的格式,默认为1
    XA语法中用到的xid值都必须和START操作使用的xid值相同

使用演示

上面给出的是命令行方式的演示过程,下面再给出java代码的使用过程:

public MysqlXADataSource xaDataSource(String url,String uname,String pwd){
        MysqlXADataSource xa = new MysqlXADataSource();
        xa.setUrl(url);
        xa.setUser(uname);
        xa.setPassword(pwd);
        return xa;
    }
    @Test
    public void testXA(){
        MysqlXADataSource testXA = xaDataSource("jdbc:mysql://xxx:3306/test", "root", "xxx");
        MysqlXADataSource trainingXA = xaDataSource("jdbc:mysql://xxx:3306/training", "root", "xxx");
        try {
            //获取每个分支事务的连接
            XAConnection test = testXA.getXAConnection();
            XAResource testXAResource = test.getXAResource();
            Connection testConn= test.getConnection();
            Statement testStatement = testConn.createStatement();

            XAConnection train = trainingXA.getXAConnection();
            XAResource trainXAResource = train.getXAResource();
            Connection trainConn = train.getConnection();
            Statement trainStatement = trainConn.createStatement();

            //生成每个分支事务对应的XID
            MysqlXid testXid = new MysqlXid("test".getBytes(StandardCharsets.UTF_8), "testDB".getBytes(StandardCharsets.UTF_8), 1);
            MysqlXid trainXid = new MysqlXid("test".getBytes(StandardCharsets.UTF_8), "trainDB".getBytes(StandardCharsets.UTF_8), 1);

            //事务分支1关联分支事务sql语句
            testXAResource.start(testXid,XAResource.TMNOFLAGS);
            testStatement.execute("UPDATE stu SET classId=3 WHERE id=1");
            testXAResource.end(testXid,XAResource.TMSUCCESS);

            //事务分支2关联分支事务sql语句
            trainXAResource.start(trainXid,XAResource.TMNOFLAGS);
            trainStatement.execute("UPDATE Salers SET SNO=\"123\" WHERE SNAME=\"123\"");
            trainXAResource.end(trainXid,XAResource.TMSUCCESS);

            //两阶段提交协议第一阶段
            int testRes = testXAResource.prepare(testXid);
            int trainRes = trainXAResource.prepare(trainXid);

            //两阶段提交协议第二阶段
            if(XAResource.XA_OK==testRes && XAResource.XA_OK==trainRes){
                //存储引擎级别事务提交
                testXAResource.commit(testXid,false);
                trainXAResource.commit(trainXid,false);
            }else {
                testXAResource.rollback(testXid);
                trainXAResource.rollback(trainXid);
            }
        } catch (SQLException | XAException e) {
            e.printStackTrace();
        }
    }

上面代码中出现了一些XAResource标记,这里解释一下:

  • XAResource#start方法开启一个分支事务
    下面提到的名词都是XAResource类中的常量,分别对应一个整数值
XAResource:
void start(Xid xid, int flags) throws XAException;

如果这里标记传入的是TMJOIN,会尝试去加入上一个被RM记录的事务中去。如果TMRESUME被设置了,会尝试去恢复xid与自己相同的并且是被挂起的事务分支。

如果没有设置上面两个标记,并且还找到了一个分支事务并且该分支事务xid与自己相同,那么会抛出异常
一般无特殊情况推荐使用TMNOFLAGS,表示不设置任何标志

  • XAResource#end方法结束当前分支事务的执行
XAResource:
void end(Xid xid, int flags) throws XAException;

这里标记的设置分为了三种情况:

  • TMSUCCESS: 该分支事务已经成功完成了

  • TMFAIL: 该分支事务执行失败,RM会标记当前事务为回滚状态

  • TMSUSPEND: 会将当前分支事务临时挂起进入未完成状态,当前事务被挂起后需要通过start方法设置TMRESUME来恢复

  • XAResource#commit方法提交当前分支事务

XAResource:
void commit(Xid xid, boolean onePhase) throws XAException;

onePhase标志是否为一阶段提交,两阶段提交协议中,如果只有一个RM参与,那么可以优化为一阶段提交。

相当于跳过了prepare一阶段提交,变成了局部事务的处理方式

XA状态转换图

XA的BUG

在Mysql 5.5之前的版本中,如果分支事务到达prepare状态,此时数据库异常重启后,可以选择对分支事务进行提交或者回滚,但是即使选择提交事务,该事务也不会被写入BINLOG日志,这会导致在使用BINLOG恢复数据时,丢失部分数据,并在如果存在从库,可能导致主从数据库的数据不一致。

此外,如果是分支事务的客户端连接异常终止的话,例如执行prepare之后退出连接,那么数据库会自动回滚未完成的事务,之所以这样做是因为对于prepare的事务,MySQL 是不会记录binlog的(官方说是减少fsync, 起到了优化的作用)。只有当分布式事务提交的时候才会把前面的操作写入binlog信息,所以对于binlog来说,分布式事务与普通的事务没有区别,而prepare以
前的操作信息都保存在连接的I0 CACHE中,如果这个时候客户端退出了,以前的binlog信息都会被丢失,再次重连后允许提交的话,会造成Binlog丢失,从而造成主从数据的不一致,所以官方在客户端退出的时候直接把已经prepare的事务都回滚了!

但是如果分布式事务情况下,其他分支事务都成功提交,这个分支回滚,会导致分布式事务的不完整,丢失部分分支事务内容。

MySql 5.7中做了以下优化: 在session断开和实例崩溃的情况下,事务都不会自动回滚,同时在XA PREPARE时,之前的事务信息就会被写入到BINLOG并同步到从库,最终再由用户决定事务回滚或者提交。

XA的性能问题

  • XA事务和本地事务以及锁表操作是互斥的,因为XA事务会锁住当前表
  • 开启了xa事务就无法使用本地事务和锁表操作
  • 开启了本地事务就无法使用xa事务

总结

1)在执行分支事务时,会将RM资源锁住,需要等到所有的RM响应,等到第二阶段执行完毕时(提交/回滚),RM的锁才会释放,在高并发场所不适用。

2)XA方案依赖于本地数据库对XA协议的支持,如果本地数据库不支持XA协议那么第三方程序(Java)将操作不了。例如许多非关系型数据库并没有支持XA。

3)MySQL对XA方案支持的不太友好,MySQL的XA实现,没有记录prepare阶段日志。

参考资源

数据库系列之MySQL分布式事务原理及实现

对XA协议的认识

《分布式事务系列教程-第四章-XA分布式事务解决方案》

mysql2阶段提交具体实现_深入理解二阶段提交协议(DDB对XA悬挂事务的处理分析)(一)…

分布式事务实战—XA两阶段提交(2PC)方案详解

书籍: 深入浅出MySQL,高性能MySQL,Innodb技术内幕

相关文章