skip to content

Many multi-version engines implement their REPEATABLE READ level as snapshot isolation. What does snapshot isolation actually guarantee, and why does that make the SQL standard's level names an unreliable guide?

level: seniorimportance: should knowfreq 45%

answer

  1. Snapshot at transaction start; readers never block
  2. First-committer-wins on same-row updates
  3. Clears all three standard phenomena, still not serializable
  4. Level names are ceilings, not behaviour specs
  5. Long snapshots pin old versions — cleanup pressure

basics

~20 s

Snapshot isolation gives every transaction a consistent view of committed data as of its start, with writers checked for conflicting updates to the same rows. It avoids all three phenomena the standard lists, yet still allows non-serializable schedules, so passing the standard's checklist does not equal serializability.

solid answer

~50 s

**Snapshot isolation (SI)** gives a transaction a consistent view of all data committed before it started; readers never block writers and writers never block readers. Concurrent updates to the *same* row are caught (first-committer-wins, the loser aborts). Against the standard's checklist, SI shows no dirty reads, no non-repeatable reads, and no phantoms — so vendors legitimately label it REPEATABLE READ, and some expose it as its own SNAPSHOT level. But the checklist is incomplete. Two transactions can each read a consistent snapshot, check an invariant, then write **different** rows; no row-level write conflict fires, yet the combined outcome matches no serial order. The lesson: the standard defines levels as ceilings on three lock-era phenomena, not as behaviour specifications. So the same level name means different things across engines, and 'REPEATABLE READ' on one product can be strictly stronger, or differently shaped, than on another. Verify the documented guarantee rather than trusting the name.

go deeper

for a junior

Say that snapshot isolation gives each transaction a stable, consistent view of data committed before it started, and that readers do not block writers.

for a middle

Add the write-conflict rule for same-row updates and explain why that makes it satisfy the standard's phenomena checklist and get labelled REPEATABLE READ.

for a senior

Explain the structural gap to serializability, the operational cost of retained versions, and how you would protect an invariant with constraints, atomic updates or explicit locks instead of trusting the level name.

for a principal

Frame it as a portability and policy risk: level names do not specify behaviour, so invariants should live in schema constraints, and engine migrations require auditing every transaction whose correctness depends on isolation semantics.

## What snapshot isolation is Multi-version concurrency control keeps several committed versions of each row. When a transaction starts (or issues its first statement, depending on the engine), it takes a **snapshot**: a marker identifying exactly which transactions had committed at that instant. From then on, every read in that transaction is served from the versions visible at that marker, plus the transaction's own uncommitted writes. Nothing another transaction commits afterwards becomes visible. That gives three properties at once: - Readers never block writers, and writers never block readers — there are no shared read locks at all. - The whole transaction sees one internally consistent state of the database, so multiple queries across multiple tables agree with each other. - Write-write conflicts are still policed: if two concurrent transactions update the same row, one commits and the other aborts (commonly described as first-committer-wins) rather than silently overwriting. ## Why it gets labelled REPEATABLE READ Run snapshot isolation against the SQL standard's three phenomena: - *Dirty read* — impossible; only committed versions are ever visible. - *Non-repeatable read* — impossible; a re-read of the same row returns the same snapshot version. - *Phantom read* — impossible for the reader; re-running a range query against a fixed snapshot returns the same rows. It clears the whole table, so it is at least as strong as the standard's REPEATABLE READ, and by the letter of the table it even satisfies SERIALIZABLE's phenomena column. Vendors therefore map it onto REPEATABLE READ (or ship it as an extra SNAPSHOT level). That is conforming behaviour, because levels are ceilings on permitted phenomena, not exact behaviour specs. ## Where the naming breaks down Snapshot isolation is **not serializable**. The gap is structural, and the phenomena list simply never described it, because the list was catalogued from lock-based implementations. The general shape: two concurrent transactions each read an overlapping set of rows, each decides something based on what it read, and each then writes a *different* row. No two writes touch the same row, so the write-conflict check never fires, and both commit. Yet each made its decision on a state that the other's commit invalidated — an outcome no serial execution could produce, because in either serial order the second transaction would have seen the first one's write. A second, subtler gap: the read-only anomaly, where a transaction that writes nothing at all observes a state inconsistent with any serial ordering of the writers around it. That is impossible under serializability and possible under SI. So 'passes the three-phenomenon checklist' and 'serializable' are different claims, and an engine can honestly advertise the former while not providing the latter. ## Practical consequences **Portability is not free.** Application code that was correct against one engine's REPEATABLE READ can be wrong against another's, because one may take a transaction-wide snapshot, another may hold shared locks to commit, and a third may add next-key locking so writers block. Migrating between engines requires re-examining every transaction whose correctness depends on isolation rather than on constraints. **Snapshot age matters.** Because a long transaction pins a snapshot, old row versions cannot be reclaimed while it runs. Long-running readers therefore cause version accumulation and storage/cleanup pressure, and can slow scans that must skip many dead versions. This is an operational property of SI that no isolation-level name conveys. **Statement-level vs transaction-level snapshots.** At READ COMMITTED, multi-version engines typically take a *new* snapshot per statement; at REPEATABLE READ/SI, one snapshot for the whole transaction. That single difference explains most of the behaviour change teams see when they raise the level, including why long transactions suddenly return 'stale' data. **How to protect an invariant under SI.** Do not rely on the level name. Options, cheapest first: express the invariant as a database constraint (unique, check, exclusion) so the engine enforces it regardless of level; perform the decision and the write in one atomic conditional statement so the write-conflict check applies; explicitly lock the rows the decision depends on so the read becomes a write-intent; or move that transaction to a true SERIALIZABLE level and handle retries. **How to talk about it in an interview.** Say what the guarantee *is* (consistent snapshot + write-conflict detection), say what it is *not* (serializable), and say why the standard's names cannot settle the question. That is the answer that distinguishes someone who has operated these systems from someone who memorized the table.

  • If snapshot isolation avoids dirty reads, non-repeatable reads and phantoms, why isn't it SERIALIZABLE?
    Because those three phenomena do not enumerate every non-serializable schedule; they were catalogued from lock-based implementations. Under snapshot isolation two transactions can read overlapping data, then write disjoint rows, so no write-write conflict is detected and both commit even though each decided based on a state the other invalidated. Serializability is defined by equivalence to a serial schedule, not by passing that checklist.
  • What operational cost does snapshot isolation add that lock-based isolation does not?
    Old row versions must be retained as long as any snapshot might still need them, so a single long-running transaction blocks reclamation of dead versions across the database. That shows up as storage growth and as scans slowing down because they skip many invisible versions. Monitoring the age of the oldest open transaction is the standard defence.
  • How does READ COMMITTED differ from snapshot isolation on the same multi-version engine?
    READ COMMITTED typically takes a fresh snapshot for every statement, so two statements in one transaction can see different committed states. Snapshot isolation takes one snapshot for the entire transaction, so all statements agree with each other. That is why raising the level makes multi-query transactions self-consistent but also makes them see data that is older by the time they finish.

Snapshot isolation is like each editor working from a photocopy of the manuscript taken at the moment they sat down: nobody sees half-written edits, and two people editing the same line are caught — but two people editing different lines can each assume the other's page is unchanged.

saying these in an interview costs you the question

  • Claiming snapshot isolation is equivalent to SERIALIZABLE because it shows none of the three listed phenomena
  • Assuming REPEATABLE READ behaves identically across engines
  • Thinking snapshot isolation prevents every anomaly involving writes, including ones spread across different rows
  • Ignoring that long snapshots block version cleanup
  • Relying on the level name in code review instead of on a constraint or an explicit lock

context