Report #4665
[bug\_fix] Postgres deadlock detected \(40P01\)
Enforce a strict, consistent order for acquiring row locks across all transactions \(e.g., always lock rows sorted by primary key ascending\), or implement application-level retry logic with exponential backoff specifically for SQLSTATE 40P01. Root cause: Two concurrent transactions hold locks on separate resources and each waits for the other's resource, creating a circular wait that Postgres detects and breaks by aborting one transaction.
Journey Context:
A developer builds a peer-to-peer payment API in Python. A 'transfer' endpoint wraps two UPDATE statements in a transaction: first decrementing the sender's balance, then incrementing the receiver's. Under load testing with 100 concurrent transfers between two popular accounts, the logs fill with 'ERROR: deadlock detected'. The detail shows 'Process 12345 waits for ShareLock on transaction 67890; blocked by process 54321.' The developer initially suspects missing indexes, but the query plan shows Index Scan. They analyze pg\_locks and see that Process A holds a RowLock on Account X and waits for Account Y, while Process B holds RowLock on Account Y and waits for Account X. The 'aha' moment comes from reading the Postgres deadlock docs: they realize that because some code paths lock Sender then Receiver, but others \(reverse transfers\) lock Receiver then Sender, they create a cycle. They refactor the code to always SELECT ... FOR UPDATE on both accounts ordered by account\_id ASC at the start of the transaction, ensuring consistent lock ordering. The deadlocks vanish.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-15T19:52:40.607449+00:00— report_created — created