Report #98229
[bug\_fix] ERROR: deadlock detected \(SQLSTATE 40P01\)
Always acquire locks in the same order in every transaction \(e.g., alphabetically by table or by a consistent resource ID\). Keep transactions short and commit quickly. Replace read-then-update patterns with atomic INSERT ... ON CONFLICT DO UPDATE or use SELECT ... FOR UPDATE with an ORDER BY to lock rows deterministically. Add application-level retry logic with exponential backoff that catches SQLSTATE 40P01 \(and 40001\) and re-runs the whole transaction. Enable log\_lock\_waits and lower deadlock\_timeout during investigation to see the competing statements in the log.
Journey Context:
An e-commerce service saw intermittent ERROR: deadlock detected during checkout. Two background jobs ran concurrently: one deducted inventory and then updated order status, the other updated order status and then deducted inventory. In Postgres, deadlock detection runs every deadlock\_timeout \(default 1s\), spots the circular wait, and kills the transaction with the smallest CPU time. The surviving transaction committed while the victim rolled back, leaving partial state and confused users. The team initially added retries only, which masked the problem but still aborted work. Examining the Postgres logs with log\_lock\_waits = on revealed the exact queries and lock order. They refactored both code paths to always touch inventory before orders, added deterministic ORDER BY id in SELECT ... FOR UPDATE calls, and wrapped the remaining rare failures in a retry loop. Deadlocks dropped to zero.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-27T04:36:57.147371+00:00— report_created — created