A-A+
Flowable6.4 – 分布式事务建表异常
前些日子,关注公众号的同学问了我一个问题,我当时的回答虽然GET到了异常发生的节点,但是并不准确,所以有了才有了这篇文章,在此也对这位同学说声抱歉。
当Flowable启动时,如果连接的是一个空库,并且在配置文件中指定了DatabaseSchemaUpdate="true",正常情况下,Flowable会自动在数据库中建立所需的表。
但是,当开启了分布式事务的时候,建表就会产生异常,输出的日志是这样的:
java.sql.SQLException: XAER_RMFAIL: The command cannot be executed when global transaction is in the ACTIVE state
我当时分析的时候,直接去看了一遍源码,最关键的代码在于new ProcessEngineImpl()时,会执行一个Command,如下:
if (processEngineConfiguration.getSchemaManagementCmd() != null) {
commandExecutor.execute(processEngineConfiguration.getSchemaCommandConfig(), processEngineConfiguration.getSchemaManagementCmd());
}
这个Command其实就是:
org.flowable.engine.impl.SchemaOperationsProcessEngineBuildProcessEngineImpl
一路追踪下去,最关键的执行代码如下:
try {
//注意这里,主要是获得connection
Connection connection = dbSqlSession.getSqlSession().getConnection();
//此处省略代码若干
BufferedReader reader = new BufferedReader(new StringReader(ddlStatements));
String line = readNextTrimmedLine(reader);
boolean inOraclePlsqlBlock = false;
while (line != null) {
if (line.startsWith("# ")) {
logger.debug(line.substring(2));
} else if (line.startsWith("-- ")) {
logger.debug(line.substring(3));
} else if (line.startsWith("execute java ")) {
//此处省略代码若干
} else if (line.length() > 0) {
if (dbSqlSession.getDbSqlSessionFactory().isOracle() && line.startsWith("begin")) {
} else if ((line.endsWith(";") && !inOraclePlsqlBlock) || (line.startsWith("/") && inOraclePlsqlBlock)) {
//这里是执行的地方
Statement jdbcStatement = connection.createStatement();
try {
// no logging needed as the connection will log it
logger.debug("SQL: {}", sqlStatement);
jdbcStatement.execute(sqlStatement);
jdbcStatement.close();
} catch (Exception e) {
} finally {
}
} else {
}
}
line = readNextTrimmedLine(reader);
}
就是在执行建表语句的时候出错了,之前我以为是单独打开了一个本地事务,导致与XA事务相斥,所以会产生异常。
但是,在写这篇文章之前,我又再去复盘了整个过程,发现并没有创建一个新的事务,建表的DDL语句执行的时候,仍旧是在XA事务内,于是我又按照关键字搜索了一下资料。
终于在stackoverflow上找到了合理的解释,具体的地址如下:
https://stackoverflow.com/questions/57553011/how-to-roll-back-ddl-statements-such-create-table-and-drop-table-while-using-jdb
其实就是如果DDL语句在事务中执行,它会自动commit,也就是所谓的隐式提交。而且文中给了一个链接,直接指向了Mysql的官方站点:
https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html
这篇官方解释就很清楚了:当事务处于活动状态时,不能在XA事务中使用导致隐式提交的语句,而恰恰DDL语句会导致隐式提交。
如果在MySQL中试验一下,比如使用以下的命令:
XA START 'SID1';
CREATE TABLE `t_test` (
`id` int(11) NOT NULL ,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
PRIMARY KEY (`id`)
);
XA COMMIT 'SID1';
也会得到相同的错误,所以DDL导致隐式提交,这才是最根本解释。
以上,如果有误,欢迎指正和讨论。
