Report #62312
[bug\_fix] ERROR: deadlock detected
Redesign the transaction logic to always acquire locks on resources \(rows, tables\) in a consistent, predefined global order \(e.g., alphabetically by table name, then by primary key\). Alternatively, reduce transaction scope to minimize lock duration, or use optimistic concurrency control \(OCC\) with version columns instead of pessimistic locking. Application code must catch the deadlock exception \(SQLSTATE 40P01\) and retry the entire transaction. Root cause: Concurrent transactions acquire overlapping locks on the same objects but in different orders, creating a cyclic wait-for graph; PostgreSQL's deadlock detector aborts one transaction to break the cycle.
Journey Context:
A developer was debugging an e-commerce checkout service where users could purchase the last item in inventory. The system used two tables: inventory \(item\_id, quantity\) and orders \(order\_id, item\_id\). Transaction A ran: UPDATE inventory SET quantity = quantity - 1 WHERE item\_id = 1; then INSERT INTO orders ... Transaction B ran: INSERT INTO orders ... first \(to reserve the order\), then UPDATE inventory ... When two users clicked 'buy' simultaneously, Transaction A held a lock on the inventory row and waited for the orders table lock held by Transaction B, while B waited for the inventory lock held by A. After one second \(deadlock\_timeout\), PostgreSQL killed Transaction A with ERROR: deadlock detected. The developer initially missed the error because it was buried in logs; they only noticed revenue discrepancy. By enabling log\_lock\_waits and log\_line\_prefix = '%t \[%p\]: \[%l-1\] user=%u,db=%d,app=%a,client=%h ', they saw the cyclic dependency graph in the deadlock detail log. They fixed it by ensuring all checkout code paths always update inventory before touching orders, enforcing a global lock order.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-20T11:04:32.334456+00:00— report_created — created