PostgreSQL Deadlock Detected
The current transaction was chosen as a deadlock victim and rolled back.
Root Cause
Two or more transactions are waiting for locks held by each other.
How to Fix
Implement retry logic in your application and ensure that transactions acquire locks in a consistent order.
Quick Summary
Postgres SQLSTATE 40P01 fires when two transactions form a lock cycle. Postgres breaks the cycle by killing one (the 'deadlock victim'). Recover by retrying with backoff after standardizing lock-acquisition order.
Key Takeaways
- Postgres SQLSTATE 40P01 = deadlock_detected; MySQL emits errno 1213 with the same meaning
- Always acquire locks in a globally consistent order across transactions (e.g. sort row IDs ascending before locking)
- Deadlock victims are safe to retry, they were rolled back, not committed in a corrupted state
- Shorter transactions reduce deadlock surface area; never hold a transaction open across user-input I/O
When to use it
- Concurrent updates to the same rows from two services
- Long-running transactions touching many rows
- Foreign-key cascades unexpectedly locking parent rows
Common Mistakes
- Retrying without exponential backoff, re-creates the deadlock immediately
- Catching the error and logging without rolling back, Postgres marks the transaction aborted; further statements fail
- Locking rows in different order across services (Service A locks user→order, Service B locks order→user)
40P01 PostgreSQL Deadlock Detected, Frequently Asked
How do I detect a deadlock in my application code?
Check the SQLSTATE: '40P01' in Postgres, ER_LOCK_DEADLOCK (1213) in MySQL. Most drivers expose this as `error.code` (`PG_DEADLOCK_DETECTED`) or `error.errno`.
Should I retry every deadlock automatically?
Yes, but cap retries (typically 3–5) and use jittered exponential backoff. Persistent deadlocks indicate a lock-order bug that retrying will not fix.
Still having issues?
Check your network logs or use our developer tools to inspect headers, decode tokens, or validate your requests.