What isolation is for · Aleksei Zhynguel
Interactive noteTransaction isolation · part 1 of 7Sep 2026 · 14 minPostgres 16

What isolation is for

Before the anomalies, the promise they break. What a transaction is, why a database runs many at once, and what it means to run them as if each were alone.

Scroll to advance. Steps 1–6 are the idea, steps 7–11 are how Postgres does it. You can switch the isolation level at any step.

{{ outShort }}
{{ aName }} t {{ bName }}
{{ e.seq }}
{{ e.t }}{{ e.s }}
Database · committed
{{ r.name }}{{ r.val }}
{{ v.t }}{{ v.s }}
{{ pnote }}
{{ outLabel }} {{ outVal }}
{{ warmFrom }} {{ warmQ }}

{{ warmFb }}

01 · One unit of work

Moving 40 from checking to savings takes two writes: take 40 out of one account, put 40 into the other. If the server dies between them, 40 has vanished.

A transaction wraps both writes so they count as one. After COMMIT both are saved. If anything fails before that, neither is.

02 · The rule in the middle

The two accounts add up to 100 before the transfer and 100 after it. In between, for a moment, they don’t: after the first write checking holds 10 and savings still holds 50.

That in-between moment is fine as long as only the transfer itself can see it. Here a report, B, runs after the transfer has finished and reads 10 + 90.

03 · Two at once

Now the report runs first and the transfer second. The report reads 50 + 50. Either order gives a correct answer, because each transaction saw the other either completely done or not started.

Running transactions one after another is called a serial order. Every serial order is correct by definition.

04 · Why not just queue them
Predict first · the diagram shows ? {{ aq3 }}
The explanation opens when you answer.

{{ fb3 }}

Running strictly one at a time would be simple and very slow. A transaction spends most of its life waiting: on disk, on the network, on your application deciding what to do next. So the database interleaves them.

Here the report reads right after the transfer’s first write. Imagine a database with no isolation at all: the report sees checking at 10 and savings at 50, and reports 60. Money that was in neither account.

05 · The promise

Isolation is the rule that makes interleaving safe. Its strongest form, SERIALIZABLE, promises that whatever the database does inside, the result is the same as some serial order.

Switch the level to SERIALIZABLE. The report reads 100, as if it had run entirely before the transfer.

06 · Discounts
Predict first · the diagram shows ? {{ aq5 }}
The explanation opens when you answer.

{{ fb5 }}

Keeping that promise has a price, so SQL also defines weaker levels. Each one lets certain mistakes through in exchange for less waiting and fewer failed transactions.

Under READ COMMITTED, the order shown here gives 140: the report read checking before the transfer and savings after it. The rest of this series is those mistakes, one per note.

Every anomaly in this series is a specific interleaving. The playground at the bottom of each note lets you build them yourself.

Deeper

You can stop here and you’ll know what the rest of the series is about. The next five steps are how Postgres gives each transaction its own view.

07 · Names that don’t quite fit

The SQL standard defines four levels by listing what each one must prevent: dirty reads, non-repeatable reads, phantoms. The definitions were written with lock-based databases in mind.

In 1995, Berenson and his colleagues showed that those definitions miss things, lost updates and write skew among them. That’s why two databases can both offer REPEATABLE READ and behave differently. This series only describes Postgres.

08 · Versions, not overwrites

Postgres doesn’t change a row in place when you update it. It writes a new version and marks the old one as replaced. Each version records which transaction created it, in a field called xmin, and which one replaced it, in xmax.

Here the transfer is transaction 101. After it commits, both versions of each row are still on disk.

09 · A snapshot is a list

To decide which versions it can see, a transaction uses a snapshot: the set of transactions that had committed at a given instant. A version is visible if its creator is in that set and its replacer isn’t.

At this moment the transfer, 101, hasn’t committed. The report’s snapshot doesn’t include it, so the report sees the old version of checking. It didn’t wait for anything to get it.

Postgres stores a snapshot compactly: the oldest id still running, the next id to be handed out, and the ids in between that were still in progress.

10 · How often you take one

The level mostly decides when snapshots are taken. READ COMMITTED takes a new one for every statement. REPEATABLE READ and SERIALIZABLE take one at the first statement and keep it until the end.

Switch between them on this order. The same two reads give 140 or 100 depending only on that choice.

11 · Where waiting still happens

Readers never wait for writers, and writers never wait for readers. Two writers on the same row are different: the second one waits until the first commits or rolls back.

What the second writer does after that wait also depends on the level. That’s the source of lost updates, in part 4.

Connections

Every idea in the series, and every step where it appears. To review, pick a term, jump to a step, and come back.

{{ kCount }}
{{ ocTerm }}

{{ ocDef }}

Related
  1. {{ r.where }}{{ r.title }}
The whole series in one table
AnomalyRead committedRepeatable readSerializableWhat stops it
Dirty readpreventedpreventedpreventedUncommitted versions are invisible · part 2, step 8
Read skewallowedpreventedpreventedOne snapshot per transaction · part 3, step 7
Lost update (read, then write)allowedone fails · 40001one fails · 40001Refusing to overwrite a newer row · part 4, step 7
Write skewallowedallowedone fails · 40001Tracking what was read (SSI) · part 5, step 6
Phantom readallowedpreventedpreventedOne snapshot per transaction · part 6, step 5
Write skew on new rowsallowedallowedone fails · 40001Predicate locks · part 6, step 9
Postgres 16 behavior, as described in chapter 13.2 of its documentation. The SQL standard allows more than this at each level.
Check yourself{{ score }}
{{ q.n }}

{{ q.q }}

{{ q.fb }}

{{ q.model }}

Three things to keep
ATOMICA transaction is all or nothing. COMMIT saves every write; anything else saves none.
ISOLATEDIsolation decides what a transaction can see of the others running beside it.
SNAPSHOTSIn Postgres, the level mostly decides when a snapshot is taken. That one choice explains the next six notes.
← All notes Found a mistake? Tell me and I'll fix it. Next: Dirty reads →