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.
Isolation levels as a ladder of promises
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)DirtyNon-repeatablePhantom
READ UNCOMMITTEDallowedallowedallowed
READ COMMITTEDforbiddenallowedallowed
REPEATABLE READforbiddenforbiddenallowed
SERIALIZABLEforbiddenforbiddenforbidden
  1. Name the anomaly you refuse to tolerate. If you cannot name it, you have not chosen a level — you have inherited one.
  2. Find the lowest rung whose row forbids every anomaly you refuse. "Lowest" is the whole skill: one rung higher is pure lost throughput.
  3. 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.
  4. 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

Checkpoint — answer before you move on

  1. 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.
  2. 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.
  3. 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?