1

我在 Postgres 数据库中看到一些无法解释的死锁。简化相关查询,死锁涉及的事务之一是:

BEGIN;
UPDATE A SET CHUNK_ID=1, STATUS='PROCESSING' WHERE ID IN (
    SELECT ID FROM A
    WHERE CHUNK_ID IS NULL
    ORDER BY O_ID
    LIMIT 1000
    FOR UPDATE 
);
COMMIT;

另一个是:

BEGIN;
UPDATE A SET STATUS='SENT' WHERE ID = 1; 
UPDATE A SET STATUS='SENT' WHERE ID = 2;
UPDATE A SET STATUS='SENT' WHERE ID = 3;
...
COMMIT; 

我的问题是这里怎么可能出现僵局?我想不出第一个事务可能导致死锁的任何情况,而不管同时运行的任何其他查询。

有没有这样的情况,即使用嵌套的 SELECT ... FOR UPDATE 的 UPDATE 可能是死锁的一部分?

谢谢

4

1 回答 1

0

(这是一个猜想,但希望是一个受过教育的猜想。)

一切都取决于 SELECT ... ORDER BY O_ID ... FOR UPDATE 锁定行的顺序。如果O_ID的顺序和ID的顺序不同,那么完全有可能出现类似这样的情况:

ID    O_ID
--    ----
1     2
2     1
  • 事务 A 锁定 ID=2 的行。
  • 事务 B 锁定 ID=1 的行。
  • 事务 A 尝试锁定 ID=1 的行,但被迫等待事务 B。
  • 事务 B 尝试锁定 ID=2 的行,但被迫等待事务 A。

警告:即使 O_ID 顺序与 ID 顺序相同,也有可能 ORDER BY 子句实际上并不能保证锁定的顺序(它只是保证返回结果的顺序)。不幸的是,这似乎没有记录。就其价值而言,Oracle 在锁定方面似乎并不(总是)遵守 ORDER BY,所以我在 PostgreSQL 下也要小心。

通常,解决死锁的方法是始终以相同的顺序锁定。假设 ORDER BY 实际上保证了锁定的顺序,您可以简单地在第二个事务中包含 SELECT ... ORDER BY O_ID ... FOR UPDATE 。或者,在第一个事务中使用 ORDER BY ID。

顺便说一句,您为什么首先明确锁定?你到底想用它来完成什么?

于 2013-02-19T19:00:16.397 回答