2

我在 Glassfish 3.1.2.2 中运行的 Hibernate (3) 和 MySQL (5.5) 遇到了一个奇怪的问题,其中自动提交标志的状态在运行时在单个服务器调用的上下文中和相同的线程。我没有明确地改变它,它最近突然开始了。这方面的代码没有显着变化。

我正在使用的代码具有各种数据访问方法,每种方法都遵循相同的模式:

  1. 从 Hibernate 会话工厂获取当前会话
  2. 开始交易
  3. 执行更新
  4. 提交事务
  5. 第 3 步和第 4 步在 try/catch 中,如果出现异常,事务将回滚。

在前两次这样的方法调用中,自动更新标志为假(getDefaultSession().connection().getAutoCommit()),但在第三次,以及之后的每一次,标志突然为真,没有说明它是如何/为什么改变的。如果标志为真,我不得不向某些方法添加解决方法代码以跳过提交,但这是一个最近才开始发生的问题,我不能去更新所有方法。

敲门声是自动提交标志在当前会话中肯定保存为 true,因为下次该线程 ID 获得会话时,状态开始为 true。如果下一次在该线程上使用的数据访问方法碰巧没有解决方法代码,则会引发异常并且状态恢复为 false。同样,我们没有明确的代码。

IE

  1. 线程 1,方法 1
    1. DAO 1:自动提交错误
    2. DAO 2:自动提交错误
    3. DAO 3:自动提交 true
    4. DAO ...n: 自动提交 true
  2. 线程 !1,方法自动提交 false
  3. 线程1,方法
    1. DAO x: 自动提交 true
    2. 例外
    3. DAO y: 自动提交 false

我发现了很多关于如何在 Hibernate 配置中启用/禁用自动提交的线程,并且我确实有配置项:

<property name="hibernate.connection.autocommit">false</property>

我还发现一些线程表明在使用多个线程时标志可能会更改为 true,但是所有这些方法(大约在更改发生时)都是从同一个初始方法和同一个线程中调用的(根据 ThreadID 确认)在 Glassfish 日志中)。

我已经阅读了 JDBC url 上的relax auto-commit 标志,但我更愿意深入了解它开始发生的原因。作为最后的手段,我可​​能不得不这样做以避免不得不更改我们所有的代码。

严重迷茫...

4

1 回答 1

0

Glassfish 4.0 遇到了同样的问题,也没有深入了解它。大多数人似乎只是设置了relaxAutoCommit标志而忘记了它。我们所做的测试表明,即使我们setAutoCommit(false)不仅返回了getAutoCommit()不正确的状态,而且如果真的像这样查询 MySQL 数据库:SELECT @@session.autocommit我们看到确实仍然为会话启用了自动提交。

我认为设置relaxAutoCommit是危险的,应该避免!如果您真的费心阅读Connector/J的文档,您会发现它relaxAutoCommit有一个明确的目的:

如果驱动连接的 MySQL 版本不支持事务,是否仍然允许调用 commit()、rollback() 和 setAutoCommit()(true/false,默认为 'false')?

设置它只是为了抑制异常是一个错误,IMO。

最终,我们实现了一个com.mysql.jdbc.ConnectionLifecycleInterceptor,发现由于某种原因,调用setAutoCommit(false)从池中返回的连接有时会无法在驱动程序的连接上实际设置自动提交,因此无法在会话上设置它。

当连接进入这种不一致的状态时,我们通过重试操作来解决这个问题。

启用 ConnectionLifecycleInterceptor 和其他日志记录后,应用程序日志中生成的一些输出如下所示:

18 Feb 16:47:07 ERROR [http-listener-1(11)] - auto-commit set to: false
18 Feb 16:47:07  INFO [http-listener-1(11)] - Autocommit before command: SetActionCommand
18 Feb 16:47:07  INFO [http-listener-1(11)] - Autocommit state is: connAutocommit=true, sessAutocommit=true
18 Feb 16:47:07  INFO [http-listener-1(11)] - SetActionCommand: host: bat424211win64, action: Checking, comment: Checking:20160218.164706.EST: Stuck Windows Check
18 Feb 16:47:07  INFO [http-listener-1(11)] - Autocommit after command: SetActionCommand
18 Feb 16:47:07  INFO [http-listener-1(11)] - Autocommit state is: connAutocommit=true, sessAutocommit=true
18 Feb 16:47:07 ERROR [http-listener-1(11)] - commit called
18 Feb 16:47:07 ERROR [http-listener-1(11)] - auto-commit should be false
18 Feb 16:47:07 ERROR [http-listener-1(11)] - Exception while executing: SetActionCommand
18 Feb 16:47:07 ERROR [http-listener-1(11)] - rollback called
18 Feb 16:47:07 ERROR [http-listener-1(11)] - auto-commit should be false
18 Feb 16:47:07  WARN [http-listener-1(11)] - Connection failure
18 Feb 16:47:07 ERROR [http-listener-1(11)] - 

ConnectionLifecycleInterceptor 正在记录我们正在调用 setAutoCommit(false) 的事实,并且在调用 SetActionCommand 之前和之后的其他日志记录正在报告 getAutoCommit() 和 session.autocommit 的结果。

Connector/J 的相关部分以及我自己的一些附加评论:

com.mysql.jdbc.ConnectionImpl:

public void setAutoCommit(final boolean autoCommitFlag) throws SQLException {
    synchronized (getConnectionMutex()) { // Mutex is 'this'
        checkClosed();

        if (this.connectionLifecycleInterceptors != null) { // Normally should be 'null'
            IterateBlock<Extension> iter = new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) {

                @Override
                void forEach(Extension each) throws SQLException {
                    if (!((ConnectionLifecycleInterceptor) each).setAutoCommit(autoCommitFlag)) { // Logged 'auto-commit set to: false'
                        this.stopIterating = true;
                    }
                }
            };

            iter.doForAll();

            if (!iter.fullIteration()) { // Only one listener (ours) AFAIK, unlikely to return here
                return;
            }
        }

        if (getAutoReconnectForPools()) { // Default for autoReconnectForPools is 'false'
            setHighAvailability(true);
        }

        try {
            if (this.transactionsSupported) { // This is 'true'

                boolean needsSetOnServer = true;

                if (this.getUseLocalSessionState() && this.autoCommit == autoCommitFlag) { // Default for useLocalSessionState is 'false'
                    needsSetOnServer = false;
                } else if (!this.getHighAvailability()) { // This is 'false'
                    needsSetOnServer = this.getIO().isSetNeededForAutoCommitMode(autoCommitFlag); // Looks like this is always 'true'?
                }

                // this internal value must be set first as failover depends on it being set to true to fail over (which is done by most app servers and
                // connection pools at the end of a transaction), and the driver issues an implicit set based on this value when it (re)-connects to a
                // server so the value holds across connections
                this.autoCommit = autoCommitFlag; // Updated value is not reflected in getAutoCommit()!  We never get here?

                if (needsSetOnServer) {
                    execSQL(null, autoCommitFlag ? "SET autocommit=1" : "SET autocommit=0", -1, null, DEFAULT_RESULT_SET_TYPE,
                            DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false);
                }

            } else {
                if ((autoCommitFlag == false) && !getRelaxAutoCommit()) {
                    throw SQLError.createSQLException("MySQL Versions Older than 3.23.15 do not support transactions",
                            SQLError.SQL_STATE_CONNECTION_NOT_OPEN, getExceptionInterceptor());
                }

                this.autoCommit = autoCommitFlag;
            }
        } finally {
            if (this.getAutoReconnectForPools()) {
                setHighAvailability(false);
            }
        }

        return;
    }
}
com.mysql.jdbc.MysqlIO:
    protected boolean isSetNeededForAutoCommitMode(boolean autoCommitFlag) {
    if (this.use41Extensions && this.connection.getElideSetAutoCommits()) { // Default for elideSetAutoCommits is 'false'
        boolean autoCommitModeOnServer = ((this.serverStatus & SERVER_STATUS_AUTOCOMMIT) != 0);

        if (!autoCommitFlag && versionMeetsMinimum(5, 0, 0)) {
            // Just to be safe, check if a transaction is in progress on the server....
            // if so, then we must be in autoCommit == false
            // therefore return the opposite of transaction status
            boolean inTransactionOnServer = ((this.serverStatus & SERVER_STATUS_IN_TRANS) != 0);

            return !inTransactionOnServer;
        }

        return autoCommitModeOnServer != autoCommitFlag;
    }

    return true; // Should always be returning 'true'
}

我很困惑为什么 setAutoCommit() 在实际做任何事情之前就返回了,除非有什么东西导致 iter.fullIteration() 返回 false。

于 2016-02-18T17:58:08.957 回答