Unit 6 · Databases · Lesson 2 of 10
Transactions, isolation anomalies and MVCC
Builds on
Step 1: Learn the ideas
- ACID and what each property actually promises
- Schedules, conflicts and conflict serializability (precedence graphs)
- Anomalies: dirty write and read, non-repeatable read, phantom, lost update, read skew, write skew
- ANSI isolation levels and why phenomenon-based definitions are ambiguous
- Two-phase locking, strict 2PL, deadlock detection and prevention
- Multi-version concurrency control and snapshot visibility
- Snapshot isolation and first-updater-wins
- Serializable snapshot isolation (SSI) in PostgreSQL
- Application-level fixes: atomic updates, SELECT FOR UPDATE, constraints, retry loops
- Long-running transactions, version bloat and VACUUM
Why it matters
Many concurrency bugs in backend systems are races between transactions rather than between threads: two requests read the same balance, both decide there is enough money, both write. Whether that can happen depends on the isolation level, and defaults are weaker than many people assume (PostgreSQL defaults to READ COMMITTED). Interviewers use these questions to see whether you can connect an invariant, such as "no double booking", to a mechanism that actually protects it.
Core ideas
ACID, read carefully. Atomicity is all-or-nothing on abort or crash, implemented with logging. Consistency means the application's invariants; the database only enforces the constraints you declare. Isolation describes what concurrent transactions may observe of each other. Durability means committed data survives crashes, under assumptions about storage.
Serializability is the reference. A schedule is serializable if its effect equals some serial order of the same transactions. Conflict serializability is a checkable sufficient condition: add an edge Ti → Tj whenever an operation of Ti conflicts with a later operation of Tj (same item, at least one write); if the graph has no cycle, the schedule is serializable.
Anomalies as interleavings.
- Dirty read: T2 reads a value T1 wrote but has not committed.
- Non-repeatable read: T1 reads x twice and sees T2's committed update in between.
- Phantom: T1 repeats a predicate query and sees rows T2 inserted.
- Lost update: both read x, both write a value computed from what they read; one write is lost.
- Write skew: both read an overlapping set, then each writes a different item; each write is fine alone, together they break an invariant (both on-call doctors go off call).
Locking. Strict 2PL takes shared locks to read and exclusive locks to write and holds them until commit; every schedule it admits is conflict-serializable, provided phantoms are blocked with predicate, range or next-key locks. The price is blocking and deadlocks, handled by detecting cycles in a waits-for graph or by prevention schemes such as wound-wait.
MVCC and snapshot isolation. Writers create new versions and each transaction reads a snapshot of committed versions, so readers and writers do not block each other. Under snapshot isolation (PostgreSQL's REPEATABLE READ) all reads come from one snapshot, and a transaction that updates a row changed by a concurrent committed transaction is aborted. That prevents lost updates on a row, but not write skew: the two transactions write different rows and never conflict on a write.
SSI. PostgreSQL's SERIALIZABLE keeps snapshot reads and tracks read-write dependencies with non-blocking predicate locks. When it sees two consecutive read-write dependencies through one transaction, a pattern every SI anomaly contains, it aborts a transaction with SQLSTATE 40001. The check is conservative, so applications must retry the whole transaction.
-- write skew under REPEATABLE READ; table doctors(id, on_call)
S1: BEGIN ISOLATION LEVEL REPEATABLE READ;
S1: SELECT count(*) FROM doctors WHERE on_call; -- 2
S2: BEGIN ISOLATION LEVEL REPEATABLE READ;
S2: SELECT count(*) FROM doctors WHERE on_call; -- 2
S1: UPDATE doctors SET on_call = false WHERE id = 1; COMMIT;
S2: UPDATE doctors SET on_call = false WHERE id = 2; COMMIT;
-- both commit and nobody is on call; under SERIALIZABLE one fails
Fixes in practice. Prefer one atomic statement (UPDATE acct SET bal = bal - 10 WHERE id = 1 AND bal >= 10), a constraint (UNIQUE, CHECK, exclusion), or explicit row locks (SELECT ... FOR UPDATE) on the rows that represent the invariant. Use SERIALIZABLE when the invariant spans rows you cannot easily lock, and always wrap it in a retry loop.
Common misconceptions
- "Isolation level names mean the same thing everywhere." PostgreSQL's REPEATABLE READ is snapshot isolation; MySQL InnoDB's uses snapshots for plain reads but its locking reads and updates act on the latest committed rows, so lost updates remain possible there. Read the documentation of the system you use.
- "Snapshot isolation is serializable." It prevents the ANSI phenomena but allows write skew and some read-only anomalies; some systems even label it SERIALIZABLE.
- "SELECT then UPDATE in one transaction is safe." Under READ COMMITTED two transactions can both read the old value; use an atomic update or a row lock.
- "SERIALIZABLE means no errors to handle." It means the database may abort you; correctness depends on retrying.
- "MVCC removes locking." Concurrent writers to the same row still wait for each other, and DDL takes table locks.
- "ACID consistency is CAP consistency." The first is about application invariants; the second is linearizability of replicated data.
How to approach it
Learn the anomalies as two-column timelines, then run them: two psql sessions reproduce lost update, write skew and a serialization failure in minutes. In interviews, start from the invariant, name the race, propose the cheapest mechanism that closes it and state its cost (blocking, aborts, retries). Senior and research conversations continue into how 2PL and SSI are implemented, how deadlocks are resolved and what long transactions do to version cleanup.
Suggested order
- 1Read DDIA Ch. 7 (Transactions) for the anomaly vocabulary, weak isolation levels and the on-call write-skew example.
- 2Read CMU 15-445 Fall 2025 Lecture #17 notes (Concurrency Control Theory): schedules, conflicts and precedence graphs.
- 3Read Lecture #18 (Two-Phase Locking) and Lecture #20 (Multi-Version Concurrency Control) notes; draw the waits-for graph for one deadlock.
- 4Read the PostgreSQL Transaction Isolation page and the Serialization Failure Handling page, noting what each level allows in PostgreSQL specifically.
- 5Reproduce lost update, write skew and a serialization failure in two psql sessions, using the Hermitage PostgreSQL scripts as a checklist.
- 6Skim the Jepsen pages on serializability and snapshot isolation to place these levels among other consistency models.
- 7For depth, read the Critique of ANSI SQL Isolation Levels with its paper guide, or implement BusTub Project #4 (MVOCC).
Paper guides: A Critique of ANSI SQL Isolation Levels
Step 2: Check off what you can do
Tap each one you can genuinely do or explain. Each earns 10 XP.
Step 3: Go deeper
PostgreSQL documentation: Transaction IsolationStart here
PostgreSQL Global Development GroupExactly which anomalies each PostgreSQL isolation level permits and how SERIALIZABLE reports failures; reread before any isolation question.
Open(opens in a new tab)Link responded, 5 Oct 2026Designing Data-Intensive ApplicationsStart here
O'Reilly Media (author's book site)Section: Ch. 7: Transactions
The clearest practical treatment of weak isolation, lost updates, write skew, phantoms and serializability implementations.
Open(opens in a new tab)Link responded, 5 Oct 2026CMU 15-445/645 Intro to Database Systems
Carnegie Mellon University Database GroupSection: Fall 2025 Lecture #20: Multi-Version Concurrency Control (notes)
How MVCC stores and finds versions, snapshot visibility and garbage collection; Lectures #17 and #18 cover serializability theory and 2PL.
Open(opens in a new tab)Link responded, 5 Oct 2026Hermitage: testing transaction isolation levels
GitHub (Martin Kleppmann)Ready-made two-session scripts that reproduce each anomaly in PostgreSQL and other databases.
Open(opens in a new tab)Link responded, 5 Oct 2026Jepsen consistency models: serializability, snapshot isolation and weaker levels
JepsenSection: Snapshot Isolation
Concise definitions of snapshot isolation and serializability and how they relate to neighboring models.
Open(opens in a new tab)Link responded, 5 Oct 2026
Step 4: Practice
Run each anomaly in two terminal sessions against a local PostgreSQL: lost update under READ COMMITTED, write skew under REPEATABLE READ, then the same write skew under SERIALIZABLE, recording which statement blocks or fails and with which SQLSTATE. Then write a small retry wrapper that re-runs the whole transaction on serialization failures. Time Based Key-Value Store is a short warm-up on looking up versions by timestamp; BusTub Project #4 is the deep option (use the current offering’s version if a newer semester is running).
- Reproduce isolation anomalies in PostgreSQL with two sessionsOpen(opens in a new tab)Not attemptedHermitage scripts + local PostgreSQL
- Time Based Key-Value StoreLeetCode(opens in a new tab)Not attemptedLeetCode
- CMU 15-445 Fall 2025 Project #4: Concurrency Control (BusTub MVOCC)Open(opens in a new tab)Not attemptedCMU 15-445
- CMU 15-445 BusTub projects (buffer pool, index, query execution, concurrency control)Open(opens in a new tab)Not attemptedCMU 15-445
Record attempts on each problem’s page (+15 XP, +10 more when solved on your own).
Step 5: Test yourself
Answer each one out loud or on paper before you finish the lesson.
- Question 1Draw the precedence graph for r1(x) w2(x) w1(x) c1 c2. Is the schedule conflict-serializable?
- Question 2Why does strict 2PL need predicate or range locks to prevent phantoms?
- Question 3What does first-updater-wins prevent, and what does it not prevent?
- Question 4Which dependency pattern does PostgreSQL’s SSI look for, and why can it abort transactions that would have been fine?
- Question 5Why is UPDATE t SET n = n + 1 safe under READ COMMITTED while read-modify-write in application code is not?
- Question 6What happens to version cleanup when a transaction stays open for hours?
You’re done when you can
- Write the two-transaction timeline for six anomalies from memory and name the weakest PostgreSQL level that prevents each.
- Reproduce write skew under REPEATABLE READ and its rejection under SERIALIZABLE in PostgreSQL, explaining the error.
- Explain in under three minutes why snapshot isolation prevents lost updates on one row but not write skew.
- Given a double-booking scenario, propose two different correct fixes and state the cost of each.
Finish the lesson
Unlocks next: Consistency models and isolation guarantees, Delivery semantics, deduplication and the outbox pattern, System design method, Write-ahead logging, recovery and durability, OLTP vs OLAP, distributed joins and distributed transactions, Concurrency control research and isolation correctness


