为了规范分布式事务的管理,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组成:
分布式事务的基本流程如下:
具体流程如下:
当每个RM都结束了全局事务的执行后,此时每个RM管理的分布式事务分支还没有提交,只是把该事务管理的业务逻辑执行完了。
进入二阶段提交阶段,在这个阶段,会先进入prepare阶段,然后再是commit或者rollback阶段。
具体流程如图:
第一阶段分为两个步骤:
第二阶段也分为两个步骤:
两阶段提交的好处是有了事务管理器进行统一管理,让事务在提交前尽可能的完成所有能完成的工作。同时两阶段提交可以保证事务的一致性,不管是事务管理器还是各个资源管理器,每执行一步操作都会被日志记录,为出现故障后的恢复提供依据。
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]
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类中的常量,分别对应一个整数值
XAResource:
void start(Xid xid, int flags) throws XAException;
如果这里标记传入的是TMJOIN
,会尝试去加入上一个被RM记录的事务中去。如果TMRESUME
被设置了,会尝试去恢复xid与自己相同的并且是被挂起的事务分支。
如果没有设置上面两个标记,并且还找到了一个分支事务并且该分支事务xid与自己相同,那么会抛出异常一般无特殊情况推荐使用TMNOFLAGS,表示不设置任何标志
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一阶段提交,变成了局部事务的处理方式
在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并同步到从库,最终再由用户决定事务回滚或者提交。
1)在执行分支事务时,会将RM资源锁住,需要等到所有的RM响应,等到第二阶段执行完毕时(提交/回滚),RM的锁才会释放,在高并发场所不适用。
2)XA方案依赖于本地数据库对XA协议的支持,如果本地数据库不支持XA协议那么第三方程序(Java)将操作不了。例如许多非关系型数据库并没有支持XA。
3)MySQL对XA方案支持的不太友好,MySQL的XA实现,没有记录prepare阶段日志。
mysql2阶段提交具体实现_深入理解二阶段提交协议(DDB对XA悬挂事务的处理分析)(一)…
书籍: 深入浅出MySQL,高性能MySQL,Innodb技术内幕
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://cjdhy.blog.csdn.net/article/details/125275556
内容来源于网络,如有侵权,请联系作者删除!