'"Deadlock Detected" (40P01) in PostgreSQL: Diagnose and Fix It'
"Deadlock detected 40P01 postgres fix — read the DETAIL lock graph, diagnose with log_lock_waits, fix with ordered locking, and add retry-on-40P01 to your app."
Your error log says ERROR: deadlock detected , SQLSTATE 40P01 , and a transaction just got killed. Don't panic and don't just retry blindly: a deadlock means two transactions each hold a lock the other wants, so Postgres aborted one of them to break the cycle. This is the deadlock detected 40P01 postgres fix walkthrough — how to read the lock graph in the error detail, how to reproduce and diagnose the ordering bug, how to fix it with consistent lock ordering, and how to add safe retry logic for the cases you can't fully prevent. Last updated: October 4, 2026 What you'll learn What a Postgres deadlock actually is (and what 40P01 tells you) How to read the DETAIL lock graph: "Process X waits for ShareLock… blocked by process Y" Diagnosing with log_lock_waits and deadlock_timeout The real fix: consistent lock ordering across all code paths Retry-on-40P01 application code (Node.js example) Upsert and foreign-key deadlock patterns, and monitoring with pg_stat_database.deadlocks What "Deadlock Detected" (40P01) Means A deadlock is a circular wait: transaction A holds a lock on row 1 and wants row 2; transaction B holds row 2 and wants row 1. Neither can proceed, ever. Postgres runs a deadlock detector (it checks every deadlock_timeout , default 1 second ) and aborts one of the transactions — the "victim" — with: ERROR: deadlock detected SQLSTATE: 40P01 DETAIL: Process 412 waits for ShareLock on transaction 89341; blocked by process 417. Process 417 waits for ShareLock on transaction 89336; blocked by process 412. Key facts: the whole transaction is rolled back, not just the statement. Postgres picks the victim arbitrarily — never write code that assumes which side survives. And 40P01 is *not* a lock timeout (that's 55P03): a lock timeout means one…