使用 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_pid
和 blockin_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
子句来避免重复处理同一行。
更多信息:
我注意到并发执行类似于
的简单且相同的查询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_pid
和 blockin_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
子句来避免重复处理同一行。
更多信息: