Laurenz explained the mechanism that can lead to deadlocks, and you already included a link yourself to a more detailed explanation by Kevin:
Here are step-by-step instructions how to reproduce a deadlock - works with plain UPDATE
the same way as it does with SELECT .. FOR UPDATE
:
Now, how to avoid the problem?
If you are going to update a substantial share or all of the table - and you can afford to - just write-lock the table. Typically, this is not the way to go. Else, three different approaches:
1. Consistent order
The manual has this advise in the chapter on deadlocks:
The best defense against deadlocks is generally to avoid them by being
certain that all applications using a database acquire locks on
multiple objects in a consistent order.
Not sure why there is still no ORDER BY
for UPDATE
. But that's what we have to work with. Lock rows with SELECT ... FOR UPDATE
in the same transaction instead - like you already tried, as your earlier question indicates. You just forgot the essential deterministic ORDER BY
:
BEGIN;
SELECT FROM foos WHERE owner_id = 123 AND unread
ORDER BY ??? -- any deterministic order, PK would be an obvious candidate
FOR UPDATE;
UPDATE foos SET unread = false WHERE owner_id = 123 AND unread;
END;
Obviously, all potentially competing transactions have to acquire locks in the same order.
2. Skip locked rows
Only process unlocked rows:
BEGIN;
SELECT FROM foos WHERE owner_id = 123 AND unread
-- ORDER BY ??? -- optional in this case
FOR UPDATE SKIP LOCKED;
UPDATE foos SET unread = false WHERE owner_id = 123 AND unread;
END;
If you are certain that skipped rows have been processed by a competing transaction doing the same, you are done here. (Are you sure?)
Else, to make sure, follow up with a check:
SELECT EXISTS (SELECT FROM foos WHERE owner_id = 123 AND unread);
Writers don't block readers and readers don't block writers, so this returns TRUE
until every last row has been updated successfully. Loop the above UPDATE
block followed by this (with appropriate delay) until you get FALSE
. Then you are done.
May be cheaper for big sets where ORDER BY
would add significant cost.
OTOH, it still can make sense to add ORDER BY
if there is a matching index ...
3. One at a time
Similar to the above, except that only a single row is updated at a time. Typically more expensive but any deadlock potential is eliminated - if done right. Consider this when processing a single row already takes a long time.
Detailed explanation (mostly also applicable to the above) and instructions: