使用 SELECT FOR UPDATE 时出现死锁

Deadlock when using SELECT FOR UPDATE

我注意到并发执行类似于

的简单且相同的查询
BEGIN;  
SELECT files.data FROM files WHERE files.file_id = 123 LIMIT 1 FOR UPDATE;
UPDATE files SET ... WHERE files.file_id = 123;
COMMIT;

导致死锁,这让我很惊讶,因为看起来这样的查询不应该造成死锁。另外:完成这样的请求通常只需要几毫秒。在这种死锁情况下,如果我 运行:

 SELECT blockeda.pid AS blocked_pid, blockeda.query as blocked_query, 
 blockinga.pid AS blocking_pid, blockinga.query as blocking_query FROM pg_catalog.pg_locks blockedl
 JOIN pg_stat_activity blockeda ON blockedl.pid = blockeda.pid
 JOIN pg_catalog.pg_locks blockingl     ON(blockingl.transactionid=blockedl.transactionid
 AND blockedl.pid != blockingl.pid)
 JOIN pg_stat_activity blockinga ON blockingl.pid = blockinga.pid
 WHERE NOT blockedl.granted;

我看到我在整个死锁期间为 blocked_pidblockin_pid 列出了两个完全相同的 select 语句。

所以我的问题是:尝试 select 同一行 FOR UPDATE 的查询导致死锁是否正常?如果是这样,在这种情况下避免死锁的最佳策略是什么?

你的命令自相矛盾。

如果 files.file_id 定义为 UNIQUE(或 PRIMARY KEY),则不需要 LIMIT 1。而且您根本不需要显式锁定。只是运行 UPDATE,因为在整个事务中只有一行受到影响,所以不会出现死锁。 (除非触发器或规则或涉及的函数有副作用。)

如果 files.file_id 不是 UNIQUE(看起来如此),那么 UPDATE 可以以任意顺序影响多行并且只有其中一个被锁定,这是死锁的一个秘诀.更直接的问题是查询没有执行您似乎想要开始的操作。

最佳解决方案取决于缺失的信息。这会起作用:

UPDATE files
SET    ...
WHERE  primary_key_column = (
         SELECT primary_key_column
         FROM   files 
         WHERE  file_id = 123
         LIMIT  1
     --  FOR    UPDATE SKIP LOCKED
         );

单个命令不需要 BEGIN;COMMIT;,同时启用默认自动提交。

如果该行已被锁定,您可能需要添加 FOR UPDATE SKIP LOCKED (or FOR UPDATE NOWAIT) 以跳过或报告错误。

您可能想添加一个 WHERE 子句来避免重复处理同一行。

更多信息: