Connecting It All Together
Concept. A complete transaction system needs a correctness standard, conflict-serializability, and engines that target it. 2PL hits it exactly, locking rows through the lock manager. MVCC trades it for non-blocking reads: its default, snapshot isolation, is a weaker bar that permits write skew, and it reaches full serializability only at the SERIALIZABLE level. Together they let thousands of transactions run concurrently, each at a chosen correctness level.
Intuition. When Mickey's playlist update and Minnie's profile edit run at the same time, 2PL serializes them only on the rows they share, the lock manager blocks them on those rows alone (not the whole table), and the resulting schedule is provably equivalent to running them one-after-the-other. All the theory shows up in a single concurrent execution.
Implementation Motivation
The earlier pages built the theory piece by piece. Here it assembles into a working transaction system.
The Goal: Implement 2PL locking (or S2PL) to produce conflict-serializable schedules and beat serial execution.
Implementation: S2PL in Practice
Sample Code for S2PL
# Strict 2PL: acquire locks as you read/write, release them all at commit.
class Transaction:
def __init__(self, txn_id):
self.id = txn_id
self.locks_held = set()
def read(self, item):
lock_manager.acquire_lock(self.id, item, "SHARED") # grow
self.locks_held.add(item)
return database.read(item)
def write(self, item, value):
lock_manager.acquire_lock(self.id, item, "EXCLUSIVE") # grow
self.locks_held.add(item)
database.write(item, value)
def commit(self):
for item in self.locks_held: # shrink: all at once
lock_manager.release_lock(self.id, item)
self.locks_held.clear()
Try it yourself: See the full implementation in the Transactions Colab
The Big Picture

Figure 1. The whole concurrency stack in one view: schedules decide the interleaving, the lock manager or MVCC enforces it, and conflict-serializability is the bar 2PL passes (MVCC meets a weaker one, snapshot isolation, by default). Each piece had its own page; here they connect into one transaction system.
Two routes, two correctness levels: 2PL makes a reader wait for a writer and produces conflict-serializable executions; MVCC keeps old versions so readers never block, producing snapshot isolation by default and conflict-serializable executions only at the SERIALIZABLE level.
Component Integration
One transaction, top to bottom:
1. Application: issues transactions (BEGIN ... COMMIT)
2. Transaction Manager: coordinates execution
3. Lock Manager (or MVCC): grants locks (2PL, conflict-serializable) or keeps versions (MVCC, snapshot isolation by default)
4. Recovery Manager: makes commits survive crashes (the next module)
Together they deliver the ACID guarantees.
The performance numbers (49 → 31 time units, ~37% speedup on the worked example; 10× to 100× on IO-bound workloads at scale) come from Microschedules & Deadlock.
The Power of Transaction Managers: Beyond Simple Locks
The same three ideas scale far past two transactions on one machine. The exact lock that serializes Mickey and Minnie here also runs stock exchanges, airline seat maps, and bank ledgers, where the same conflict-serializability guarantee holds across millions of transactions a second. Pushing 2PL across many machines is its own problem, and that is where Module 5 picks up.
Figure 2. The same protocol stretches three ways. It distributes, holding two-phase locking across machines in different cities; it recovers, replaying the write-ahead log after a crash so committed work survives; and it scales, running millions of transactions a second on stock exchanges and bank ledgers. Same core, different operating point.
Next
Change Tracking: Log the Diff → Concurrency control protects transactions from each other. Recovery protects them from crashes. Together they make ACID real, and recovery starts with one idea: log the diff, don't copy.