【问题标题】:When is `SELECT ... FOR UPDATE` lock released?`SELECT ... FOR UPDATE` 锁何时释放?
【发布时间】:2016-03-30 15:55:44
【问题描述】:

我正在开发一个允许多个用户访问数据库(MySQL)的程序,并且在不同的时间我得到一个 SQLException: Lock wait timeout exceeded

使用以下方式创建连接:

conn = DriverManager.getConnection(connString, username, password);
conn.setAutoCommit(false);

所有调用都通过这段代码:

try { 
    saveRecordInternal(record);
    conn.commit();
} catch (Exception ex) {
    conn.rollback();
    throw ex;
}

saveRecordInternal 有一些内部逻辑,保存给定的记录。一路上的某个地方是我怀疑是问题的方法:

private long getNextIndex() throws Exception {
    String query = "SELECT max(IDX) FROM MyTable FOR UPDATE";
    PreparedStatement stmt = conn.prepareStatement(query);

    ResultSet rs = stmt.executeQuery();
    if (rs.next()) {
        return (rs.getLong("IDX") + 1);
    } else {
        return 1;
    }
}

如果需要,此方法由saveRecordInternal 在其操作过程中的某处调用。 由于目前我无法控制的原因,我不能使用自动增量索引,而且无论如何,某些内部程序逻辑需要插入的索引。

我认为调用conn.commit()conn.rollback() 就足以释放锁,但显然不是。所以我的问题是——我应该在getNextIndex 中使用stmt.close() 还是rs.close()?是在事务提交或回滚之前释放锁,还是只是确保在调用conn.commit()conn.rollback() 时确实释放了锁?

我还有什么遗漏/完全错误的吗?

编辑:在锁定发生时,所有连接的客户端似乎都有响应,目前没有查询正在进行,但关闭所有连接的客户端确实可以解决问题。这让我认为,即使事务(假设?)通过提交或回滚结束,锁也会以某种方式被保留。

【问题讨论】:

    标签: java mysql jdbc locking


    【解决方案1】:

    即使不关闭 StatementResultSet 是个坏主意,但该函数似乎不对您收到的错误负责。函数,getNextIndex() 正在创建本地 StatementResultSet 但不关闭它。在那里关闭它们或在saveRecordInternal() 中创建那些StatementResultSetobjects 并作为参数传递,或者如果在您的起点创建并一次又一次地重用则更好。最后,在不再需要时关闭它们(按以下顺序 - ResultSet, Statement, Connection)。

    错误仅表示某个数据库对象(连接、表、行等)上存在锁,而另一个线程/进程同时需要它但必须等待(已锁定)但由于超过预计等待。

    请参阅 How to Avoid Lock wait timeout exceeded exception.? 以了解有关此问题的更多信息。

    总而言之,这是您的环境特定问题,需要在您的机器上调试并启用大量日志记录。

    希望对你有帮助!!

    【讨论】:

    • 谢谢。所以,如果我理解正确,使用stmt.close()不会释放锁,直到事务提交或回滚?
    【解决方案2】:

    从上面的陈述中,我没有看到任何保持打开状态的锁! 通常,MySql 应该在调用提交或回滚时或连接关闭时释放锁。

    你的情况

    SELECT max(IDX) FROM MyTable FOR UPDATE
    

    会导致锁定整个表,但我认为这是预期的逻辑!您锁定表,直到插入新行,然后释放它以让其他人插入。 我会测试:

       SELECT IDX FROM MyTable FOR UPDATE Order by IDX Desc LIMIT 1
    

    确保锁即使在锁定单行时也保持打开状态。

    如果不是这种情况,我可能是由于表非常大而导致锁超时。

    【讨论】:

    • 多大才算非常大?目前它有 varchar(100),其他是 int、date 或 varchar(15)
    • 这实际上非常小,但这取决于您的环境。如果您的应用程序不需要 Max(IDX),您可以在实际插入新行时调用此查询。
    • 在这种情况下要考虑的另一件事是 Select Max(IDX) From MyTable FOR UPDATE 实际上将对所有受影响的行(在这种情况下为全部)执行行级锁定。锁定整个表可能是一个更好的主意:LOCK TABLES MyTable READ 然后解锁它!
    • 会使用Order by IDX Desc LIMIT 1 只锁定第一行,还是全部锁定?
    • 我不确定 MySQL 中的确切实现!我希望它只锁定返回的行,但实际上我可能会根据条件锁定所有返回的行。在这种情况下,没有 where 子句,因此它取决于内部实现。这实际上是一个有趣的测试:)
    【解决方案3】:

    所以,我认为这里发生的是:您的查询试图在某个表上执行,但该表被另一个进程锁定。因此,在旧锁不会从表中释放之前,您的查询将等待执行,一段时间后如果锁没有释放,您将获得锁超时异常。

    您还可以查看表级锁和行级锁。 简而言之:表级锁锁定整个表,直到锁在那里,您希望能够在同一张表上执行任何其他查询。 尽管 行级锁将锁定特定行,因此除了该行之外,您还可以对表执行查询。

    您还可以检查是否有任何查询长时间在表上运行,因此您无法执行另一个查询并出现异常。如何检查这将因数据库而异,但您可以通过谷歌搜索您的特定数据库以查找查询以获取数据库上的打开连接或长时间运行的查询。

    【讨论】:

    • 是的,我认为某处有一把锁,问题是 - 如何保持锁定?所有客户似乎都完成了他们的任务,但锁仍然存在(我将编辑我的问题来解释这一点)。
    • 所以,这里可能发生的情况是:客户端对数据库执行了一些查询,客户端释放了连接。但查询已开始在数据库上执行。因此,即使客户端已释放连接,查询仍在运行。
    • 这没有意义,因为关闭客户端的时候问题就解决了,所以不能是孤立的连接/查询。
    • 这只是说代码有一些保持客户端连接打开的漏洞。
    猜你喜欢
    • 1970-01-01
    • 2014-03-10
    • 1970-01-01
    • 2019-06-13
    • 1970-01-01
    • 2020-11-24
    • 1970-01-01
    • 2016-12-01
    • 1970-01-01
    相关资源
    最近更新 更多