使用 Rownum 时出现数据库死锁?

Posted

技术标签:

【中文标题】使用 Rownum 时出现数据库死锁?【英文标题】:Database Deadlocks when using Rownum? 【发布时间】:2009-02-04 17:19:28 【问题描述】:

如果我从应用程序中的多个线程调用以下代码,是否存在死锁风险?用于为此连接到数据库的事务在此调用之前打开,并在它返回后关闭。 应用:Java 数据库:甲骨文

  FUNCTION reserveWork(in_batch_id NUMBER,
                       in_work_size NUMBER,
                       in_contentType_id NUMBER) RETURN NUMBER IS
    rows_reserved NUMBER := 0;

  BEGIN
    UPDATE
          D_Q1
    SET
          DQ1_BAT_ID = in_batch_id
    WHERE
         DQ1_BAT_ID is null
         AND DCT_ID = in_contentType_id
         AND ROWNUM < (in_work_size + 1);

    rows_reserved := SQL%ROWCOUNT;

    RETURN (rows_reserved);

  END;

【问题讨论】:

【参考方案1】:

要发生死锁,您必须具备这两个条件。

    每个事务必须有多个锁。

    必须以不同的顺序抓住锁。

条件 1 为真,因为您的每个线程都锁定了多行。 条件 2 在理论上是正确的,因为返回的行的顺序是不确定的。例如,线程 1 可能会尝试更新第 1、2、3 行,而线程 2 可能会尝试更新第 3、2、1 行。

实际上,Oracle 可能总是以相同的顺序返回行,因此它可能永远不会死锁。无论如何,准备好处理 ORA-00060 错误并重新提交请求。

另一个想法是分两步完成。第一个过程执行 SELECT * WHERE ... FOR UPDATE NO WAIT 来锁定行,如果不返回 ORA-00054,则第二个过程执行实际更新。否则你重试。

无论哪种方式,请确保将 CREATE TABLE 中的 INITTRANS 设置为将同时更新表的客户端数量。

【讨论】:

哇,我在下面的 Gary 的回答中看到了 Tom Kyte 的回答。很酷!这意味着如果事务阻塞,它将重新启动,因此它不应该死锁。【参考方案2】:

如果您在同一张表上运行多个 UPDATE,则存在一定的死锁风险。

特别是因为我在您的代码中看不到 COMMIT 或 ROLLBACK?我想这是在 JDBC 中完成的?

UPDATE 花费的时间越长,死锁风险就越高。

【讨论】:

是的,提交/回滚会在调用此函数后立即完成,具体取决于是否生成了异常。【参考方案3】:

当事务 A 锁定一条记录然后必须等待事务 B 解锁记录,而事务 B 正在等待已被事务 A 锁定的记录时,就会发生死锁。

Oracle 有一个非常复杂的机制来处理更新过程中对表的更改。见

http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:11504247549852

通常,事务运行的时间越长,事务更改的数据越多,死锁的风险就会增加。我想说这不太可能死锁,但可能会“排队” - 如果您有三个或四个并发会话运行此 SQL,每个会话将具有相同的 SQL 执行路径,将标识相同的行进行更新,一个会先找到他们,其他人会等待。当第一个事务完成时,另一个事务将重新获取记录,发现它们已更改,然后按照 Tom Kyte 的文章中所述重新启动并选择下一组行。

如果您使用的是 11g,则可以使用 SKIP LOCKED。它存在于早期版本中,但未记录在案。因此,您将自行承担使用风险。

http://download.oracle.com/docs/cd/B28359_01/server.111/b28286/statements_10002.htm#SQLRF01702

这样你就可以

SELECT primary_key BULK COLLECT INTO pk_variable_array FROM D_Q1
WHERE    DQ1_BAT_ID is null
AND DCT_ID = in_contentType_id
AND ROWNUM < (in_work_size + 1)
FOR UPDATE SKIP LOCKED;
--
FORALL i in 1..pk_variable_array
 UPDATE D_Q1
 SET DQ1_BAT_ID = in_batch_id
 WHERE primary_key = pk_variable_array(i)

【讨论】:

以上是关于使用 Rownum 时出现数据库死锁?的主要内容,如果未能解决你的问题,请参考以下文章

同时执行UPDATE时出现死锁

运行弹簧批处理作业的多个实例时出现死锁

将新记录插入特定表时出现死锁

使用 ReadFile 和 WriteFile 时出现死锁

使用重定向 I/O 与子进程通信时出现死锁

访问 StackExchange.Redis 时出现死锁