Mycat事务管理分析

针对MyCat1.5版本,对MyCat的事务管理机制进行简要分析:

1. MyCat事务管理

1.1 事务开启

MyCat默认执行SQL不开启事务,使用事务时需要显式地开启。目前支持set autocommit = 0 、 BEGIN 、START TRANSACTION 三种方式来开启事务。我们建议使用 set autocommit = 0来开启事务。

MyCat的事务控制在会话级别,对应于MyCat源码里面的NonBlockingSession类。事务开启后,该会话所有后续的SQL执行都将进入事务,直到收到提交(commit)或者回滚(rollback)。

1.2 事务提交

当MyCat收到客户端发出commit语句,表示需要执行事务的提交逻辑。当前MyCat实现的事务为弱XA事务(强XA事务还在研发当中)。

1.2.1 事务提交流程分析

mycat-tx-flow

如上流程图所示,事务提交首先判断是否需要回滚,如果需要回滚,则不给用户提交。进入提交主流程,分为单节点路由和多节点路由来处理:

  • 对于路由到单节点的事务提交,无须考虑更多,直接发送commit命令包到对应的后端MySQL连接
    • 对于路由到多节点的事务提交,需要细分如下处理流程:
    • 如果事务内处理的表都是全局表,那么跟单节点的处理一致,不需要考虑更多,直接发送commit命令包到对应的后端MySQL连接
  • 如果事务内处理的表操作跨越多个分片,判断分布式事务是否被允许,如果不允许,中断事务并提示用户rollback;如果允许,则对于所有路由涉及到的后端连接,执行commit,在最终的OK包收到以后再通知用户commit成功。

1.2.2 事务提交代码

ServerConnection类的commit方法为事务提交主要入口,判断是否需要回滚,不需要回滚则进入处理事务提交主流程:

1
2
3
4
5
6
7
8
9
10
11
/**
* 提交事务
*/
public void commit() {
if (txInterrupted) {
writeErrMessage(ErrorCode.ER_YES,
"Transaction error, need to rollback.");
} else {
session.commit();
}
}

事务提交主流程体现在NonBlockingSession类的commit方法里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void commit() {
final int initCount = target.size();
if (initCount <= 0) {
ByteBuffer buffer = source.allocate();
buffer = source.writeToBuffer(OkPacket.OK, buffer);
source.write(buffer);
return;
} else if (initCount == 1) {
BackendConnection con = target.elements().nextElement();
commitHandler.commit(con);
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("multi node commit to send ,total " + initCount);
}
checkDistriTransaxAndExecute();
}
}

对于多节点事务提交的逻辑在checkDistriTransaxAndExecute方法里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void checkDistriTransaxAndExecute() {
if(!isALLGlobal()){
switch(MycatServer.getInstance().getConfig().getSystem().getHandleDistributedTransactions()) {
case 1:
// rollback();
source.writeErrMessage(ErrorCode.ER_NOT_ALLOWED_COMMAND, "Distributed transaction is disabled!Please rollback!");
source.setTxInterrupt("Distributed transaction is disabled!");
break;
case 2:
multiNodeCoordinator.executeBatchNodeCmd(SQLCmdConstant.COMMIT_CMD);
LOGGER.warn("Distributed transaction detected! Targets:" + target);
break;
default:
multiNodeCoordinator.executeBatchNodeCmd(SQLCmdConstant.COMMIT_CMD);
}
} else {
multiNodeCoordinator.executeBatchNodeCmd(SQLCmdConstant.COMMIT_CMD);
}
}

1.3 事务回滚

当MyCat收到客户端发出的rollback语句,表示需要执行事务的回滚逻辑。Rollback的流程处理较commit简单地多,主要逻辑为对于所有涉及到的路由节点,发送rollback命令包,并在发送完rollback命令包收到响应后,对于发送OK包给用户。回滚代码入口在ServerConnection的rollback方法里:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 回滚事务
*/
public void rollback() {
// 状态检查
if (txInterrupted) {
txInterrupted = false;
}
// 执行回滚
session.rollback();
}

与处理commit一样,将rollback逻辑封装到NonBlockingSession的rollback方法里面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void rollback() {
final int initCount = target.size();
if (initCount <= 0) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("no session bound connections found ,no need send rollback cmd ");
}
ByteBuffer buffer = source.allocate();
buffer = source.writeToBuffer(OkPacket.OK, buffer);
source.write(buffer);
return;
}
rollbackHandler = new RollbackNodeHandler(this);
rollbackHandler.rollback();
}

最后交由RollBackNodeHandler去发送rollback命令包。

2. 弱XA事务的优劣

前面提到MyCat目前的分布式事务实现为弱XA事务,具有对应的优势和劣势:

## 2.1 优势

较强XA事务,不会产生更多的性能消耗。

2.2 劣势

无法保证强一致性(强XA事务可以保证),跨多个节点的提交,如果前面节点提交成功,后面节点提交失败,这个时候用户必须回滚,但是只能回滚那些提交失败节点上的操作,对于已经发送了commit的节点,无法进行回滚。  

坚持原创技术分享,您的支持将鼓励我继续创作!