A-A+

Flowable6.4 – 分布式事务建表异常

2019年12月16日 技术, 默认 暂无评论 阅读 2,127 次

前些日子,关注公众号的同学问了我一个问题,我当时的回答虽然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导致隐式提交,这才是最根本解释。

以上,如果有误,欢迎指正和讨论。

觉的不错?可以关注我的公众号↑↑↑

给我留言

Copyright © 字痕随行 保留所有权利.   Theme  Ality 京ICP备14039894号

用户登录

分享到: