【问题标题】:How to properly handle InnoDB deadlocks in Java/JDBC?如何正确处理 Java/JDBC 中的 InnoDB 死锁?
【发布时间】:2013-05-27 01:25:21
【问题描述】:

我在这里以理论为基础,我想确保我的所有基础都被涵盖。

我已经阅读了很多关于 InnoDB 和 Java 的内容,以及无论您运行什么查询,死锁是如何发生的。尽管我对理论和最佳实践非常了解,但对于如何在发生死锁时实现重新发布事务的全部捕获机制却一无所知。

是否有特定的例外需要注意?异常仅在我调用connection.commit() 后引发,还是在我执行PreparedStatement 后立即发生?事情是否应该在循环中运行并限制循环运行的次数?

我基本上只需要一个简单的 Java 代码示例来说明通常如何处理这件事。由于我不确定因素在哪里,例如,我是重新实例化 PreparedStatement 对象还是先关闭它们等等,这一切都非常令人困惑。 ResultSet 对象也是如此。

编辑:我应该提到我正在处理事务,将自动提交设置为 0 等。

编辑 2: 我是否在使用这个伪代码的正确轨道上?我不知道

do
{
    deadlock = false

    try
    {
        // auto commit = 0
        // select query
        // update query
        // delete query
        // commit transaction
    }
    catch (DeadLockSpecificException e)
    {
        deadlock = true
    }
    finally
    {
        // close resources? statement.close(), resultset.close() etc?
        // or do I reuse them somehow and close them after the do/while loop?
        // this stuff confuses me a lot too
    }
}
while (deadlock == true);

【问题讨论】:

    标签: java mysql innodb deadlock database-deadlocks


    【解决方案1】:

    您的代码基本上是正确的。发生死锁时引发的异常是SQLException。异常的getSQLState() 方法提供返回一个错误代码,该代码提供additional information 关于实际错误。

    您还应该在两次尝试之间等待一小段时间,以免服务器负载过多。

    如你所料,设置最大尝试次数,否则你可能会陷入无限循环。

    最终代码可能如下所示:

    boolean oops;
    int retries = 5;
    Connection c = null;
    Statement s = null;
    ResultSet rs = null;    
    
    do
    {
        oops = false;
        c = null;
        s = null;
        rs = null;
        try
        {
            c = openConnection();
            s = c.createStatement();
            rs = s.executeQuery("SELECT stuff FROM mytable");
            fiddleWith(rs);
        }
        catch (SQLException sqlex)
        {
            oops = true;
            switch(sqlex.getErrorCode()())
            {
                case MysqlErrorNumbers.ER_LOCK_DEADLOCK:
                    // deadlock or lock-wait time-out occured
                    break;
                ...
            }
            Thread.sleep(1000); // short delay before retry
        }
        finally
        {
            if (rs != null) try {
                rs.close();
            } catch (SQLException e) {
                // some error handler here
            }
    
            if (s != null) try {
                s.close();
            } catch (SQLException e) {
                // some error handler here
            }
    
            if (c != null) try {
                c.close();
            } catch (SQLException e) {
                // some error handler here
            }
    
        }
    }
    while (oops == true && retries-- > 0);
    

    显然上面的代码是次优的。您可能希望区分连接时发生的错误和执行时发生的错误。您还可以检测到,在出现一些错误之后,再次尝试的希望很小(例如,错误的凭据或 SQL 语法错误)。

    你问了很多问题,但我会尽力一一回答:

    是否有特定的例外需要注意?

    是的,见上文:SQLException 是那些,getErrorCode()getSQLState() 提供了更多信息。

    只有在我调用connection.commit()之后才抛出异常吗?

    java.sql 包中几乎所有类的所有方法都可以抛出 SQLException

    事情是否应该在一个循环中运行并限制循环运行的次数?

    是的,见上文。

    我是否[需要]重新实例化PreparedStatement 对象?

    显然,您不能在两个查询之间重新创建PreparedStatement。您只需要在再次调用executeQuery() 之前为您的参数设置新值。当然,如果您需要执行另一个查询,则需要一个新的PreparedStatement

    ResultSet 对象也是如此

    Statement.executeQuery() 返回一个(新的)ResultSet 对象,表示查询的结果。您自己永远不会创建这样的对象。理想情况下,您会尽快致电ResultSet.close() 以释放内存。

    强烈建议大家关注this tutorial第二章(“处理SQL语句”)。

    【讨论】:

    • 当我问Is the exception only thrown after I call connection.commit()? 时,我的意思是专门针对发生死锁时抛出的异常。死锁错误是否只能在我调用 connection.commit() 时发生,或者它是否会在我执行更新或查询时发生,即使我禁用了自动提交?
    • 是的,在事务过程中的任何时候都可能需要锁(例如,SELECT stuff FROM mytable FOR UPDATE 阻塞,直到在mytable 上获得排他锁)。因此,随时可能发生死锁。更多信息here.
    • 你会说这或多或少是在正确的轨道上吗? paste2.org/30txAnkX 我使用有限的线程,所以我真的不想用线程睡眠调用来挟持一个人质。那么除了等待之外,这看起来是否正确?
    • 您的代码看起来不错。我强烈建议在尝试之间稍作等待,因为如果发生死锁,那么它很可能在不久的将来再次发生。但是您比我更了解您的应用程序中事务的典型持续时间。
    • 其实它通常是相反的(如果我的理解是正确的),因为它本质上是两个交易处于死锁状态。一个继续正常,另一个遭受死锁异常。因此,遭受死锁的事务可以安全地重试,因为获胜的事务已经获得了它想要的锁。无论如何,非常感谢您的帮助!享受赏金。
    猜你喜欢
    • 2011-07-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多