Transaction deadlock storm

Two transactions politely wait for each other to let go — and so neither one ever does.

The idea

A deadlock is a standoff. Transaction T1 holds a lock on row A and now wants row B. Meanwhile T2 holds B and wants A. Each is blocked waiting for a lock the other refuses to release. They will wait forever.

The database draws a wait-for graph: an edge from every blocked transaction to whoever holds the lock it wants. When that graph contains a cycle, a deadlock exists. The engine can't satisfy everyone, so it picks a victim, rolls it back, and lets the survivor proceed. A "storm" is many such cycles forming at once under heavy contention.

Press play to watch two transactions deadlock.

How it works

The lock manager tracks who holds each lock and who is queued for it. On every grant request — or on a periodic timer — it walks the wait-for graph. A cycle means deadlock; the engine rolls back the cheapest victim, often the youngest transaction or the one holding the fewest locks, and returns a deadlock error the client should retry.

# Wait-for graph deadlock detection (cycle search)
def has_deadlock(waits_for):
    # waits_for[t] = set of transactions t is blocked behind
    WHITE, GREY, BLACK = 0, 1, 2
    color = {t: WHITE for t in waits_for}

    def dfs(t):
        color[t] = GREY
        for u in waits_for.get(t, ()):
            if color[u] == GREY:        # back-edge => cycle
                return True
            if color[u] == WHITE and dfs(u):
                return True
        color[t] = BLACK
        return False

    return any(color[t] == WHITE and dfs(t) for t in waits_for)

Cost & trade-offs

PropertyDetail
DetectionCycle search over the wait-for graph, O(V + E) per scan
ResolutionRoll back one victim — its work is lost and must retry
ThroughputStorms waste CPU on doomed work; tail latency spikes
SignalsRising deadlocks/sec, retry rate, lock-wait time

Watch out for

Worked example

Two transfers run at once. T1 does debit(A); credit(B); T2 does debit(B); credit(A). T1 locks A, T2 locks B. Now T1 waits for B and T2 waits for A — a 2-cycle. The detector picks T2 as victim, rolls it back, and T1 completes. The fix: have both transfers lock accounts in account-id order, so both grab A first and one simply queues behind the other — no cycle.

Check yourself

1. What graph structure proves a deadlock exists?

2. What single discipline prevents most deadlocks?

Coach note: if a pick doesn't land, give it another pass — the reasoning is what sticks.