使用SELECT FOR UPDATE时出现死锁

时间:2019-01-19 01:33:26

标签: sql postgresql concurrency sql-update deadlock

我注意到并发执行类似的简单且相同的查询

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语句。

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

1 个答案:

答案 0 :(得分:2)

您的命令矛盾。

如果已将npm install react-scripts --save (或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;

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

您可能想添加一个COMMIT;子句,以避免重复处理同一行。

更多信息在这里: