Databases & Storage · Daily Card
The Level Names Tell You What Cannot Happen, Not How It Works
Four isolation levels, taught from the anomalies upward — not from the labels down.
Each rung is a promise about what cannot happen to you — and a bill for what it costs.
Core Concept
READ COMMITTED is not a description of an implementation. It is a description of a thing
that cannot happen to you. The four isolation levels are not four algorithms — they are four
promises about which anomalies you will not see, and that is exactly why the names confuse people.
So build the ladder from the anomalies, never from the names. One anomaly sits between each pair of
rungs, and every rung you climb is paid for in either concurrency or
aborted transactions.
(The mechanism underneath — versioned rows, snapshot isolation, write skew — is already on the
shelf from the MVCC card. It gets one clause here; the mapping is the point.)
Key Components — the three anomalies and the top rung
Bottom rung · permits
Dirty read
You read a value another transaction wrote but never committed. It can vanish on rollback — you
already acted on a fact that officially never existed.
Only READ UNCOMMITTED allows it. Forbidding it is the entire gap between the
bottom two rungs.
Second rung · permits
Non-repeatable read
You read the same row twice inside one transaction and get two different values, because
someone committed in between.
READ COMMITTED permits it; REPEATABLE READ forbids it.
Third rung · permits
Phantom read
You run the same query twice and get a different set of rows, because a committed
INSERT matched your predicate. Result set, not a single row.
That distinction is why REPEATABLE READ can forbid non-repeatable reads and still
permit phantoms.
Top rung · forbids all three
Serializable
The outcome is promised to be identical to running the transactions one after another — whatever
the engine does internally to earn that.
Price: concurrency, or transactions aborted and retried.
How It Works — the ladder is a permission table
| Level (SQL standard) | Dirty | Non-repeatable | Phantom |
| READ UNCOMMITTED | allowed | allowed | allowed |
| READ COMMITTED | forbidden | allowed | allowed |
| REPEATABLE READ | forbidden | forbidden | allowed |
| SERIALIZABLE | forbidden | forbidden | forbidden |
- Name the anomaly you refuse to tolerate. If you cannot name it, you have not chosen a level — you
have inherited one.
- Find the lowest rung whose row forbids every anomaly you refuse. "Lowest" is the whole skill: one
rung higher is pure lost throughput.
- Pay the toll consciously. Tightening a level never removes work; it moves work from your code into
the engine's locks, versions, or abort-and-retry loops.
- Then verify against the actual engine, because the standard's rows are floors — see below.
The falsification. The comforting belief is that the name tells you what your
database does. It does not. The SQL committee defined these levels by the anomalies it had seen,
so a level is a floor, not a specification: an engine may forbid more than the name requires, and the name
will not tell you. Postgres REPEATABLE READ is really snapshot isolation, which
forbids phantoms the standard permits at that rung; Oracle has no real READ UNCOMMITTED;
MySQL InnoDB's REPEATABLE READ behaves differently from Postgres's under
concurrent updates. The same word buys different guarantees on different engines — so code that was
correct on one can be silently wrong on another.
Where you will actually use this
- Reading your real isolation level. Open two sessions, set the level you think you have, and
try to produce each anomaly in turn. The one you cannot produce is the guarantee you actually own.
- Migrations and engine swaps — where a level name travels with the SQL but the semantics do not.
- Balances, inventory, seats: any invariant spanning two rows. Weaker here does not make you
faster, it makes you wrong faster. Serializable is not a performance dial you avoid by default.
- Reporting and billing jobs that read many rows over seconds — phantom exposure is why totals
stop summing.
- Per-transaction sizing: hot read-only paths down one rung, correctness-critical writes up one.
Level is a property of the transaction, not of the database.
Checkpoint — answer before you move on
- Your transaction reads the same row twice and gets the same value both times — but a query with a
WHERE clause returns one extra row on the second run. Which anomaly is that, and which is the lowest
level that forbids it?
One is about a row, the other about a result set. The ladder treats them separately, on purpose.
- Two engines both advertise REPEATABLE READ and yet your code breaks after
migrating between them. Using the words "floor, not specification", why is that expected rather than a bug?
Name the concrete difference Postgres makes at that rung.
- You are told "turn the isolation level down, it's slowing us". What is the one question that decides
whether this is a real optimisation or a correctness regression?
Does the invariant live in one row, or across two?