Skip to content
Transactions

The four anomalies, and what each level costs

Dirty read, non-repeatable read, phantom, and write skew. Postgres never permits the first, permits the next two at READ COMMITTED, and refuses all four at SERIALIZABLE.

16 min · runs against the shop dataset

The isolation levels are defined by which anomalies they allow. Every one of these can be produced by hand in the isolation lab, in two sessions, in under a minute — and the difference between reading about a phantom and making one happen is most of the learning.

The dirty read, which cannot happen

Reading a row another transaction has written and not committed. The standard permits it at READ UNCOMMITTED; Postgres never produces one, because MVCC has no mechanism for showing one transaction's unfinished work to another. Ask for READ UNCOMMITTED and you quietly get READ COMMITTED — which is what this engine does too, and the lab's first scenario is exactly this.

labMake the same query return two different answers, then raise the level and stop it.

The non-repeatable read

The same query, twice, in one transaction, giving two different answers — because another transaction committed a change in between. Permitted at READ COMMITTED, where every statement takes a fresh snapshot. Prevented at REPEATABLE READ, where the whole transaction shares the one it started with.

The phantom

The same range query returning a different set of rows, because another transaction inserted one that matches. Prevented at REPEATABLE READ in Postgres — the standard only requires SERIALIZABLE to prevent it, and Postgres's snapshot happens to be strong enough that it does so anyway.

labLose an update, then watch REPEATABLE READ refuse the second writer instead.

Write skew, and why it needs SERIALIZABLE

Two transactions each read a set of rows, each check a condition that still holds, and each write a different row. Neither one wrote what the other read, so no row-level conflict is detected — and the two results together violate a rule that each of them individually preserved. Two doctors both going off call because the other is still on.

REPEATABLE READ permits this. Only SERIALIZABLE refuses it, and it does so by tracking read-write dependencies between transactions and aborting one when they form a cycle. The cost is that some perfectly fine transactions get aborted too, which is why every serializable writer needs a retry loop.