Chapter 20: Deadlocks — Detection, Prevention, and Recovery

A deadlock occurs when two or more transactions are waiting for each other to release locks, creating a cycle where none can proceed.

How Deadlocks Happen

Transaction A: Transaction B: BEGIN; BEGIN; UPDATE accounts SET ... UPDATE accounts SET ... WHERE id = 1; -- locks row 1 WHERE id = 2; -- locks row 2 UPDATE accounts SET ... UPDATE accounts SET ... WHERE id = 2; -- WAITS (B has it) WHERE id = 1; -- WAITS (A has it) DEADLOCK! Both waiting forever.

Detection

PostgreSQL detects deadlocks automatically (checks every deadlock_timeout, default 1 second). It kills one transaction with:

ERROR: deadlock detected
DETAIL: Process 1234 waits for ShareLock on transaction 5678;
        blocked by process 5678.
        Process 5678 waits for ShareLock on transaction 1234;
        blocked by process 1234.

Prevention Strategies

-- NOWAIT: fail immediately instead of waiting
SELECT * FROM accounts WHERE id=1 FOR UPDATE NOWAIT;

-- SKIP LOCKED: skip rows that are locked (great for job queues)
SELECT * FROM jobs WHERE status='pending'
ORDER BY created_at LIMIT 1 FOR UPDATE SKIP LOCKED;

Recovery

When a deadlock is detected, one transaction is aborted. Your application must retry:

// Application-level retry loop (pseudocode)
for (retries = 0; retries < 3; retries++) {
    try {
        begin_transaction();
        do_work();
        commit();
        break;  // success
    } catch (DeadlockError) {
        rollback();
        sleep(random_backoff);  // wait before retry
    }
}
Key Takeaways