Two-Phase Locking

Concept. A transaction holds many locks across its life, and Two-Phase Locking (2PL) is the discipline for using them: every transaction grows its lock set first and shrinks it second, never reacquiring. Follow that one rule and locking yields a conflict-serializable schedule, with no need to test it. Strict 2PL, the version real databases ship, holds every lock until commit.

Intuition. Locking one row at a time is not enough. When Mickey reads a balance, releases the lock, then writes it back, Minnie can slip into the gap and the result is garbage. 2PL closes that gap: Mickey holds every lock he takes until he is done acquiring, so no one can interleave between his read and his write. The transaction looks atomic to everyone else.

A lock protects one row, in two modes: shared for reads, exclusive for writes. But a transaction holds many locks across its life. Release one too early and another transaction slips into the gap, reading work that may still be undone. The rule that prevents it is Two-Phase Locking.

Two-Phase Locking (2PL): Growing then Shrinking

Locks are the primitive. The protocol for when to acquire and release them is Two-Phase Locking.

Two-Phase Locking (2PL) gives every transaction two phases: a growing phase that only acquires locks, then a shrinking phase that only releases them. The first unlock flips growing to shrinking, and the transaction can never acquire again.

Two-Phase Locking: the lock count rises through the growing phase, peaks, then falls through the shrinking phase.

Figure 1. 2PL: the lock count only rises, then only falls. The peak is the moment the transaction holds everything it will ever need.

Strict 2PL (S2PL) is the version real databases ship: hold every lock until commit or abort, then release them all at once.

Strict 2PL: the lock count rises through the growing phase, plateaus while the transaction runs, then drops to zero in one step at COMMIT.

Figure 2. Strict 2PL: locks rise, plateau, then drop to zero in one step at commit. Nothing is released early.

The two-phase rule makes locking correct. As long as every transaction grows then shrinks, the resulting schedule is provably equivalent to some serial run. You don't have to verify schedules; you verify that everyone played by the rule. The proof that this yields a conflict-serializable schedule lives on Correctness; 2PL is the protocol that earns that guarantee.

The Cost of Releasing Early: Cascading Aborts

Why does Strict 2PL wait until commit to release anything? Because releasing early opens a failure that regular 2PL cannot stop.

Two protocols. Under regular 2PL, T1 writes x and unlocks early, T2 reads the uncommitted value, T1 aborts, and T2 must abort too, a cascading abort. Under strict 2PL, T1 holds the lock to commit, T2 waits, and T2 only ever reads a committed value, so there is no cascade.

Figure 3. Release a lock early (regular 2PL) and another transaction can read work you later undo, so its abort cascades into theirs. Strict 2PL holds every lock to commit, so no one ever builds on uncommitted work. It is the sous-chef who hands over a sauce only once the head chef has approved it.

What 2PL buys, and what it costs. It prevents lost updates and dirty reads, ships in Postgres, MySQL, SQL Server, and Oracle, and still lets non-conflicting transactions run in parallel. The costs: a transaction blocks on any conflicting lock, holding locks until commit is conservative, and two transactions can end up waiting on each other in a cycle.

That last cost is the one new risk locks create. Two transactions, each holding a lock the other needs, wait forever. Microschedules & Deadlock is where that risk gets detected and broken.