Skip to main content
databases

Isolation levels

12 min read
Fully authored

Read committed, repeatable read, serializable — what each protects against.

The I in ACID stands for Isolation — the property that concurrent transactions don't interfere with each other. Perfect isolation (serializable) is expensive, so the SQL standard defines four levels, each allowing some specific anomalies in exchange for higher concurrency: Read Uncommitted, Read Committed, Repeatable Read, and Serializable.

Understanding which anomalies each level prevents is one of the most-asked database interview questions and one of the most common sources of production bugs. Get it wrong and you double-charge customers, lose posts, ship duplicate orders. Get it right and your application scales without exotic locking gymnastics.

The catch: every database interprets these levels differently. Postgres's REPEATABLE READ actually implements snapshot isolation. Oracle's READ COMMITTED implements a per-statement snapshot. MySQL InnoDB's REPEATABLE READ has a special quirk called "phantom row avoidance via gap locks." The SQL-92 standard is a rough guideline; the actual behavior is per-database. This is why Berenson et al. (1995) wrote a famous paper "A Critique of ANSI SQL Isolation Levels" showing the standard was underspecified.

The papers

  • Gray et al. (1976) — the original ACID paper. Coined the term.
  • ANSI SQL-92 (1992) — standardized the 4 isolation levels + 3 anomalies.
  • Berenson et al. (1995) — "A Critique of ANSI SQL Isolation Levels." SIGMOD. Showed the standard is underspecified and introduced snapshot isolation formally.
  • Adya et al. (2000) — "Generalized Isolation Level Definitions." ICDE. Cleaner formal treatment.

The four anomalies you must know

Dirty read
Read data written by an uncommitted transaction. If that transaction later rolls back, you saw data that never officially existed.
Example: T1 UPDATEs a row, T2 reads that update, then T1 rolls back → T2 saw a phantom value.
Non-repeatable read
Read the same row twice in one transaction and get different values because another transaction committed in between.
Example: T1 reads balance=$500, T2 UPDATEs and commits balance=$600, T1 reads again and sees $600.
Phantom read
Same query returns different rows the second time — new rows appeared because another transaction inserted them.
Example: T1: SELECT * FROM orders WHERE user='alice' returns 3 rows. T2 INSERTs a new alice order. T1 re-runs → 4 rows.
Write skew
Two transactions read overlapping data, each updates a different part based on what the other read. Both commit. Combined effect violates an invariant.
Example: The hospital on-call example from MVCC. Two doctors both go off-call because each sees the other is still on.

The four levels and what they prevent

Isolation levelDirty readNon-repeatable readPhantom readWrite skew
Read Uncommitted❌ possible❌ possible❌ possible❌ possible
Read Committed✅ prevented❌ possible❌ possible❌ possible
Repeatable Read✅ prevented✅ prevented❌ possible*❌ possible
Serializable✅ prevented✅ prevented✅ prevented✅ prevented
* Postgres's implementation of Repeatable Read actually prevents phantom reads (it's snapshot isolation). MySQL InnoDB uses gap locks to prevent phantoms at RR too. The SQL standard allows phantoms at RR.

Read the table like this: at Read Committed, you're protected from dirty reads but not from anything else. At Repeatable Read, dirty reads AND non-repeatable reads are prevented. At Serializable, all four anomalies are gone.

Interactive: watch each anomaly happen

Interactive: pick an anomaly to see it play out
Dirty read
Prevented at isolation level: Read Committed or higher
T1
UPDATE accounts SET balance=$1M WHERE id=1
T2
SELECT balance FROM accounts WHERE id=1 → $1M !!
T1
ROLLBACK — the $1M never actually existed
T2
Acted on ghost data. Congratulations, you accidentally shipped a Ferrari.

Per-database reality check

DatabaseDefault levelStrongest levelNote
PostgreSQLRead CommittedSerializable (SSI)Its 'Repeatable Read' is actually snapshot isolation
MySQL InnoDBRepeatable ReadSerializable (locks a lot)Gap locks prevent phantoms at RR
OracleRead CommittedSerializable (snapshot iso, admits write skew)No 'Repeatable Read' at all
SQL ServerRead CommittedSerializableOptional SNAPSHOT and READ_COMMITTED_SNAPSHOT modes
CockroachDBSerializableSerializable (only option)Retryable errors — apps must retry
SpannerSnapshot reads by defaultExternal consistencyUses TrueTime for global serializability
MongoDB (v4.0+)Session-based snapshotreadConcern:'snapshot'Pre-4.0 had no multi-doc transactions
DynamoDBRead CommittedSerializable (transaction API)Transaction API limited to 25 items

Notice how the same isolation level means different things in different databases. Postgres's REPEATABLE READ actually gives you snapshot isolation — not what the SQL standard defines. Oracle's REPEATABLE READ doesn't exist at all. MongoDB pre-4.0 didn't even have multi-document transactions. Always test what your specific database does; don't trust the level name alone.

Applied in real systems

PostgreSQL
Deep dive

Postgres — Read Committed default, Serializable via SSI

Default is Read Committed (per-statement snapshot). Repeatable Read = full snapshot isolation. Serializable (since 9.1) = Serializable Snapshot Isolation via predicate locks — expect occasional serialization failures that you retry.

Read the deep dive →
MySQL InnoDB
Deep dive

MySQL InnoDB — Repeatable Read default

Default is Repeatable Read with gap locks that suppress phantoms (unusual choice — most others default to Read Committed). Serializable available but rarely used in practice (locks everything).

Read the deep dive →
Oracle
Deep dive

Oracle — Read Committed + Serializable only

Oracle only supports Read Committed (default) and Serializable. No Repeatable Read. Their Read Committed is actually per-statement snapshot (not the SQL standard flavor). Their Serializable is snapshot isolation, which admits write skew.

Read the deep dive →
CockroachDB
Deep dive

CockroachDB — Serializable by default

CockroachDB defaults to Serializable — the strongest level. Achieved via optimistic MVCC + write intents + retry-on-conflict. App developers must handle retryable serialization errors. Cost: higher latency; benefit: no write-skew.

Read the deep dive →
MongoDB (v4.0+)
Deep dive

MongoDB — snapshot isolation via readConcern majority

MongoDB 4.0+ supports multi-document transactions. readConcern:"snapshot" gives snapshot isolation. Isolation levels aren't exposed in SQL-standard terms — it's all snapshot- based with explicit majority write concern for linearizability.

Read the deep dive →
DynamoDB
Deep dive

DynamoDB — Read Committed + Transactional API

Standard reads = Read Committed. Transactional API (TransactWriteItems, TransactGetItems) provides atomic multi-item operations with serializable isolation (limited to 25 items per transaction).

Read the deep dive →
SQL Server
Deep dive

SQL Server — 5 levels including SNAPSHOT

Supports Read Uncommitted, Read Committed, Repeatable Read, Serializable, and SNAPSHOT (their name for MVCC snapshot isolation, off by default — enable via ALTER DATABASE).

Read the deep dive →
Google Spanner
Deep dive

Spanner — external consistency (stronger than serializable)

Uses TrueTime + Paxos to give external consistency: if T1 commits before T2 starts (real-time), every observer sees T1 before T2. This is strictly stronger than serializable. Bounded to the atomic-clock uncertainty (~7ms).

Read the deep dive →

Key takeaways

  • 4 SQL standard levels: Read Uncommitted → Read Committed → Repeatable Read → Serializable. Each prevents progressively more anomalies.
  • 4 anomalies: dirty read, non-repeatable read, phantom read, write skew. First three are in the SQL standard; write skew was recognized later.
  • Every database interprets these differently. Postgres RR = snapshot isolation. MySQL RR = SI + gap locks. Oracle has no RR.
  • Default isolation varies wildly: Postgres + Oracle default Read Committed; MySQL defaults Repeatable Read; CockroachDB defaults Serializable.
  • Write skew is the killer anomaly that standard Serializable doesn't always catch — snapshot isolation allows it. Postgres SSI + CockroachDB use predicate locks to prevent it.
  • For interviews: know what each level prevents, be able to construct a phantom-read scenario, and understand why Serializable costs the most.

References

  • Berenson et al. (1995) — A Critique of ANSI SQL Isolation Levels. The definitive practical treatment.
  • Adya et al. (2000) — Generalized Isolation Level Definitions.
  • Kleppmann (2017) Designing Data-Intensive Applications, Chapter 7 is the modern reference.
  • Postgres, MySQL, Oracle, CockroachDB docs — always check the actual behavior for your engine.

Practice what you just read

Every foundation concept has a companion quiz to close the loop.