Reputation: 119
I noticed that concurrent execution of simple and identical queries similar to
BEGIN;
SELECT files.data FROM files WHERE files.file_id = 123 LIMIT 1 FOR UPDATE;
UPDATE files SET ... WHERE files.file_id = 123;
COMMIT;
lead to deadlock which is surprising to me since it looks like such queries should not create a deadlock. Also: it is usually takes only milliseconds to complete such request. During such deadlock situation if I run:
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;
I see both of my identical select statements listed for blocked_pid
and blockin_pid
for whole duration of the deadlock.
So my question is: Is it normal and expected for queries that try to select same row FOR UPDATE
to cause deadlock? And if so, what is the best strategy to avoid deadlocking in this scenario?
Upvotes: 0
Views: 128
Reputation: 656411
Your commands are contradicting.
If files.file_id
is defined UNIQUE
(or PRIMARY KEY
), you don't need LIMIT 1
. And you don't need explicit locking at all. Just run the UPDATE
, since only a single row is affected in the whole transaction, there cannot be a deadlock. (Unless there are side effects from triggers or rules or involved functions.)
If files.file_id
is not UNIQUE
(like it seems), then the UPDATE
can affect multiple rows in arbitrary order and only one of them is locked, a recipe for deadlocks. The more immediate problem would then be that the query does not do what you seem to want to begin with.
The best solution depends on missing information. This would work:
UPDATE files
SET ...
WHERE primary_key_column = (
SELECT primary_key_column
FROM files
WHERE file_id = 123
LIMIT 1
-- FOR UPDATE SKIP LOCKED
);
No BEGIN;
and COMMIT;
needed for the single command, while default auto-commit is enabled.
You might want to add FOR UPDATE SKIP LOCKED
(or FOR UPDATE NOWAIT
) to either skip or report an error if the row is already locked.
And you probably want to add a WHERE
clause that avoids processing the same row repeatedly.
More here:
Upvotes: 2