分布式事务介绍
使用分布式关系型数据库后,一个事务如果涉及到多个物理数据库节点操作,可能会出现部分物理节点处理成功、部分失败的中间状态。按照传统的数据库操作方式无法保障数据的一致性及可用性,这就是在分布式数据库中需要解决的分布式事务问题。
分布式事务解决方案
这里主要列出由DRDS提供的几种分布式事务解决方案。
补偿型事务方案
基本原理
- 应用发起的事务涉及多个后端数据库节点的修改。
- 在COMMIT前,DRDS会保存整个分布式事务中涉及的所有SQL。
- 当COMMIT部分出错的时候,DRDS会给应用返回处理成功。
- DRDS记录事务的出错节点,让事务补偿器进行事务补偿。
- DRDS事务补偿器自动对事务中出错节点进行事务补偿,记录补偿结果。
- 应用在进入下一环节时,需要检查以上数据是否完整。
基本流程
- 启动分布式事务:dt start [tab]; 会返回事务ID。
- 执行SQL:如果SQL涉及到的表在某些节点中处于事务补偿状态,这些表在该节点中的数据不能被访问, 因此SQL执行的时候会被锁住或者返回错误。
- commit分布式事务:commit的时候会先执行次要节点,然后再执行主要节点。主要节点是最后一条SQL对应的唯一节点。
- 检查是否有wanrings返回:show warnings。
- 如果有warnings返回,使用事务ID,查询补偿是否完成:dt status tid。
补偿示例
public void transferOwn throws Exception {
public static final String name = "com.mysql.jdbc.Driver";
public static final String user = "drdsUser";
public static final String password = "*********";
Class. forName (name);//指定连接类型
String url = ”ip:port”;
String schema = "schemaName";
Connection c = DriverManager.getConnection (String. format( "jdbc:mysql://%s/ %s ?user=%s&password=%s&useUnicode=true&characterEncoding=utf-8",
url, schema, userName, password));
c.setAutoCommit (false);
Statement s = c.createStatement();
//开启分布式事务
s.execute ("dt start");
//执行业务逻辑
s. execute ("xxx;");
c.commit();
}
基于XA的事务方案
XA的定义
XA是X/Open DTP定义的两阶段提交协议,是交易中间件与数据库之间的接口规范(即接口函数),交易中间件用它来通知数据库事务的开始、结束以及提交、回滚等,提交或回滚事务必须产生一致的结果(全部提交或全部回滚)。XA接口函数由数据库厂商提供。通常情况下,交易中间件与数据库通过XA接口规范,使用两阶段提交来完成一个全局事务,XA规范的基础是两阶段提交协议。
原理
分布式XA事务利用物理数据库提供了对XA事务的支持,实现跨数据库事务的一致性。为保证事务的原子性(Atomicity)和一致性(Consistency),使用二阶段提交,将事务提交分成PREPARE和COMMIT两个阶段:
- PREPARE阶段中,数据节点准备好所有事务提交所需的资源(例如加锁、写日志等);这个阶段保证了事务数据的高可用性:数据库挂了恢复后,也会恢复还没提交的PREPARE事务。
- COMMIT阶段中,各个数据节点才真正提交事务。
当用户进行一个分布式XA事务时,DRDS作为事务管理器角色。DRDS会屏蔽XA事务实现的细节。对用户而言,跟普通事务是一样的流程。
XA事务流程
- XA事务开启:执行UDAL XA START。这时DRDS会返回这个XA事务的XID。
- XA事务执行:执行各种语句。这时DRDS跟普通事务一样的处理。
- XA事务提交 : 执行COMMIT或ROLLBACK。 DRDS首先等待所有数据节点(MySQL服务器)PREPARE成功,之后再向各个数据节点发送COMMIT请求。
XA示例
以下是业务连接DRDS的事务示例。
public static final String url = "jdbc:mysql://127.0.0.1/employee";
public static final String name = "com.mysql.jdbc.Driver";
public static final String user = "drdsUser";
public static final String password = "*********";
Class.forName (name);//指定连接类型
try (Connection conn = DriverManager. getConnection (drdsUrl, drdsUser, password)) {
//开启事务,获取DRDS事务全局唯一的xid
try (Resul tSet set = statement. executeQuery(”UDAL XA START”)){
while (set.next()) {
xid=set.getString(1);
}
}
st. execute("insert into custamr values (id, name) (1,"test1") ;");//分片1执行语句
st. execute("insert into custamr values (id, name) (2,"test1") ;");//分片2执行语句
conn.commit() //事务提交
}catch (SQLException e) {
conn. rollback();
}
建议
解决分布式事务的推荐方法就是尽量规避分布式事务,事务边界越大(或者单个SQL所执行的数据分片数),那么系统的锁冲突概率越高,系统越难以扩展,性能越低。因此,若想将系统做到很好的扩展性,那么一个重要的原则就是想办法划小事务边界,并尽可能让事务的边界限制在单台机器内。