避免更新死锁

Posted

技术标签:

【中文标题】避免更新死锁【英文标题】:Avoid deadlock from for update 【发布时间】:2020-10-25 11:16:27 【问题描述】:

如何释放另一个会话的行级锁以避免for update死锁? 我有一张桌子

这是代码,

DECLARE
CURSOR c1 IS
    SELECT id
      FROM tasks;
ls_id NUMBER;
vaf varchar2(1000);
BEGIN
OPEN c1;
LOOP
    FETCH c1
        INTO ls_id;
  vaf := 'select * from  tasks where id =''' || ls_id ||
           ''' for update of id';
    EXECUTE IMMEDIATE vaf;
    UPDATE tasks
       SET status = 'B'
     WHERE id = ls_id;
    EXIT WHEN c1%NOTFOUND;
END LOOP;
CLOSE c1;
commit;
END;

如果我首先在一个会话中运行 for updatetasks where id=4 row,然后在 plsql 开发人员 IDE 测试中的另一个会话中运行上述块窗口并逐行执行。循环继续并在 id=4 处陷入死锁。这里我在循环之后提交,因此 id=1,2 和 3 将不会有更新。如果我在之后提交update 语句它将更新记录到 id = 3 并在 id = 4 处出现死锁。是的,将提交放在循环中并不是一个好主意。

所以为了避免死锁,我使用了跳过锁定。

SELECT id FROM tasks FOR UPDATE SKIP LOCKED;

这个解决了 id=4 的死锁,而是跳过锁定的记录 id=4 并更新剩余的记录。

不允许终止会话。还有其他可能的方法来更新锁定的记录吗? 因为我正在运行一个调度程序,它正在更新重要的 trans。所以我不需要避免或跳过锁定的记录,也不需要调度程序没有死锁。有可能吗?

【问题讨论】:

您要更新的列也是外键列吗?如果是这样,该列是否已编入索引?如果没有,请尝试在其上创建索引。根据您的数据库版本,锁定发生在未索引的外部列上。也许它会有所帮助并且不会花费太多尝试。 “还有其他可能的方法来更新锁定的记录吗?” 没有。另一个会话有锁,在该会话释放它之前您不能触摸该记录。如果您提供有关您尝试实现的业务逻辑的更多详细信息,我们可能会提供一些建议。事实上,我们所能做的就是以一种困惑的方式查看您的代码。 【参考方案1】:

因此,您正在更新任务表,将 每一行 上的 task.status 的值设置为“B”。您的代码的问题是您从任务中选择所有行来驱动您的处理,无论它们是否被锁定,然后在循环中处理它时尝试锁定每个单独的行。从逻辑上讲,您似乎想要做的是每次运行时更新任务中的所有行。唯一可靠的方法是从一开始就锁定所有行。

您的代码逻辑可以简化为:

DECLARE
   CURSOR c1 IS
      SELECT *
      FROM tasks
      FOR UPDATE NOWAIT;
   row_locked EXCEPTION;
   PRAGMA EXCEPTION_INIT(row_locked, -54);
BEGIN
   OPEN c1;
   UPDATE tasks SET status = 'B';
   COMMIT;
EXCEPTION
   WHEN row_locked THEN
      DBMS_OUTPUT.PUT_LINE ( 'Table is busy' );
      ROLLBACK;
      IF c1%ISOPEN THEN CLOSE c1;
END;

但是,如果您想在不同的会话中可能同时执行此代码,则会导致阻塞锁潜在的死锁。

也许添加WHERE status != 'B' 条件可能会减少机会。

如果没有,请考虑使用 AQ 序列化更新请求,这将有助于对进程队列进行排序。这是一般性建议,因为我们需要有关此表的完整用法的更多详细信息才能给出具体建议。

【讨论】:

【参考方案2】:

您是否尝试过SELECT FOR UPDATE SKIP LOCKED

DECLARE
CURSOR c1 IS
    SELECT id
      FROM tasks;
ls_id NUMBER;
vaf varchar2(1000);
BEGIN
OPEN c1;
LOOP
    FETCH c1
        INTO ls_id;
  vaf := 'select * from  tasks where id =''' || ls_id ||
           ''' for update of id skip locked';
    EXECUTE IMMEDIATE vaf;
    UPDATE tasks
       SET status = 'B'
     WHERE id = ls_id;
    EXIT WHEN c1%NOTFOUND;
END LOOP;
CLOSE c1;
commit;
END;

如果你想避免死锁,尝试使用子句skip locked。另外请记住,如果您指定 SKIP LOCKED 并且表被另一个会话以独占模式锁定,那么数据库将不会返回 SELECT 语句的结果,直到表上的锁被释放。

【讨论】:

感谢先生的回复。在我运行块后,它在 id=4 处陷入僵局。 vaf := 'select * from tasks where id =''' || ls_id || ''' for update of id skip locked'; 嗨。死锁是在 ID=4 上产生的,您在另一个会话中运行进程对吗??你想用 select 来更新什么?? 如果您向我解释您要达到的目标,也许我可以为您提供比使用 select 进行更新更好的想法。

以上是关于避免更新死锁的主要内容,如果未能解决你的问题,请参考以下文章

怎么避免mysql死锁

数据库死锁处理方法

SQL 更新导致死锁

许多更新出现死锁错误

多次插入和更新导致死锁 c#

更新存储过程中的死锁