0 XP · 0 scenes

Transactions, ACID and isolation levels

Caching & Databases · lesson 8 of 8 · about 30 min

Transactions, ACID and isolation levels

What a database promises when many things happen at once: atomicity through the log, and then the part everyone gets wrong, isolation. Replay dirty reads, lost updates, phantoms and write skew against four isolation levels in a real tiny engine, read the grid of what each level allows, see what stronger isolation costs in retries, and learn the cheap fixes. Tap any for a plain-language card.

0 / 9 sections done
  1. 1What a transaction is4 min
  2. 2Isolation6 min
  3. 3The grid4 min
  4. 4The price4 min
  5. 5Fixing it in code4 min
  6. 6Which fix?2 min
  7. 7Who lives by this1 min
  8. 8Interview angle2 min
  9. 9Break It: write skew4 min

Words in this lesson10Tap one for its meaning.

What a transaction is

Move 100 from account A to account B. Between the debit and the credit, the server loses power.

Atomicity and durability come from the : before touching any data pages the database writes what it is about to do to a log on disk. After a crash it replays the committed entries and discards the uncommitted ones. Without a transaction the crash leaves the debit applied and the credit missing, and 100 has vanished.

Crash between the two steps with no transaction: the total drops from 1,000 to 900. Wrap it in a transaction and the crash changes nothing.

Account A400
Account B500
Total (should stay 1,000)900

Crash after the debit, before the credit: A is 400 and B is 500, so 100 has disappeared. Two separate statements give no all-or-nothing guarantee.

A deterministic illustration with starting balances 500 and 500 and a transfer of 100.

Isolation

The same two transactions, the same order of steps, four different outcomes.

This is a real (tiny) transaction engine. Choose an anomaly and an isolation level, and it replays the same schedule of steps. Read the timeline, then the verdict. The levels are, from weakest to strongest: read uncommitted, read committed (the PostgreSQL default), repeatable read (snapshot isolation) and serializable.

Pick “Lost update” and step through the levels: lost at read committed, refused at repeatable read. Then try “Write skew”, which only serializable stops.

Two requests each add to a counter: read 100, then write read + 10 and read + 20.

  • 1T1 begins
  • 2T2 begins
  • 3T1 reads n = 100
  • 4T2 reads n = 100
  • 5T1 writes n = 110 (+10)
  • 6T1 commits
  • 7T2 writes n = 120 (+20)
  • 8T2 commits
Anomaly happened?yes
Transactions abortednone

Read committed: Final value 120: one of the two additions (+10) was silently lost, 130 was expected.

The engine implements read uncommitted (reads see other transactions' tentative writes), read committed (latest committed value), snapshot isolation (a fixed snapshot plus first-committer-wins) and a serializable check that aborts a writing transaction whose reads changed under it.

The grid

Which level protects against which problem? Run every scenario against every level and read the table.

Three things to notice. First, the ANSI names are a rough guide, not a precise one: real databases differ. PostgreSQL's repeatable read is , which also prevents phantoms but not write skew; MySQL's InnoDB default is repeatable read with different details. Second, write skew is the quiet one that survives snapshot isolation. Third, serializable is the only level that makes the whole table safe, at the price of retries.

Scan the write-skew row: it is the last anomaly to disappear, and only at serializable.

ScenarioRead uncommittedRead committedRepeatable readSerializable
Dirty readANOMALYsafesafesafe
Non-repeatable readANOMALYANOMALYsafesafe
Lost updateANOMALYANOMALYaborts oneaborts one
Write skewANOMALYANOMALYANOMALYaborts one
Phantom rowANOMALYANOMALYsafesafe

Read each row from left to right: every stronger level removes anomalies the weaker one allowed. Defaults in practice: PostgreSQL, Oracle and SQL Server use read committed, MySQL InnoDB uses repeatable read. Neither is serializable by default, so write skew and lost updates are your code's problem unless you ask for more.

Generated by running the five schedules at all four levels. “aborts one” = the database aborted a transaction with a conflict error.

The price

If serializable is the safe one, why isn't everything serializable?

The simulation runs groups of concurrent transactions that each read two rows and write one, with some accesses concentrated on a few hot rows, and counts how many would be aborted at each level. Read committed never aborts (it just allows anomalies); snapshot isolation aborts on write-write conflicts; serializable also aborts on read-write conflicts.

Concentrate half of the traffic on five hot rows: serializable aborts a third of transactions. Spread it out and the aborts nearly disappear.

  • Read committed
    0% aborted (anomalies allowed)
  • Snapshot isolation
    6.4% aborted
  • Serializable
    17.9% aborted
Retries needed under snapshot isolation6.4%
Retries needed under serializable17.9%

With 10 transactions overlapping and 30% of accesses landing on 5 hot rows, 17.9% of transactions must be retried under serializable against 6.4% under snapshot isolation. Every retry is wasted work and extra latency, and retries can collide again.

3,000 seeded rounds. A transaction aborts under snapshot isolation if a concurrent committed transaction wrote a row it wrote; under serializable also if one wrote a row it read. 1,000 ordinary rows plus the hot ones.

Fixing it in code

You do not need serializable to stop the most common bug. You need to stop reading and writing in two steps.

The same options appear everywhere: compare-and-set in key-value stores, conditional writes in DynamoDB, ETags in HTTP. Choose one and watch what the two concurrent additions (+10 and +20 on a counter of 100) produce.

Start with the bug (120), then try each of the three fixes (130).

SELECT n FROM counter; -- both read 100 -- app computes n + 10 / n + 20 UPDATE counter SET n = :new; -- last write wins
Final counter (should be 130)120
Updates lost+10
Retries needed0

Read, compute in the app, write back (the bug): final value 120. Both transactions read 100 before either wrote, so the later write replaces the earlier one: 120 instead of 130.

Deterministic outcome of two concurrent additions under read committed; the explanations follow standard PostgreSQL behaviour.

Which fix?

Six situations, four answers.

Use the weakest tool that is actually safe for the problem. Each answer is worth 20 XP.

Ask: can the change be one statement? is it one row or a rule across rows? is a stale read acceptable?

Question 1 / 6

Increment a page-view counter from many servers.

Tip: press 1 to 4 to answer, Enter for the next one.

Who lives by this

From the 1995 critique to production defaults.

Four published sources on isolation.

The critiqueBerenson et al. (1995)

'A Critique of ANSI SQL Isolation Levels'

The paper showed that the SQL standard's definitions of isolation levels were ambiguous and incomplete, and defined snapshot isolation and anomalies such as write skew that the standard's phenomena did not capture.

A careful defaultPostgreSQL

read committed, plus true serializable

PostgreSQL's default is read committed; its repeatable read is snapshot isolation; and since version 9.1 its serializable level uses serializable snapshot isolation, which detects dangerous patterns of read-write dependencies and aborts a transaction, requiring applications to retry.

A different defaultMySQL InnoDB

repeatable read with MVCC

InnoDB defaults to repeatable read, using multi-version concurrency control for consistent reads and gap locks to limit phantoms for locking reads, so the exact behaviour differs from PostgreSQL's snapshot isolation even though the level has the same name.

Strict serializability across the planetGoogle Spanner

external consistency

Spanner provides serializable transactions with external consistency, meaning that if one transaction commits before another starts, the timestamps reflect that order, using TrueTime commit wait to achieve it across data centres.

Interview angle

What interviewers actually ask.

Six questions, each worth 20 XP. Isolation levels are among the most common database interview topics.

Name the anomaly, name the level that prevents it, and mention that stronger levels mean retries.

Question 1 / 6

What does atomicity guarantee?

Tip: press 1 to 3 to answer, Enter for the next one.

Break it

The hospital rule says at least one doctor must stay on call. Both doctors clicked “go off call” at the same moment.

The same shape hides in many real rules: two bookings for one room, two withdrawals from joint accounts that together overdraw, two usernames claimed at once. Fixes: use serializable isolation and be ready to retry; lock the rows you read (SELECT … FOR UPDATE on the whole set); or enforce the rule with a database constraint or a single row that represents the invariant (so there is a write-write conflict). Set the numbers.

Two doctors, two requests, rule of at least one: snapshot isolation leaves zero on call. Switch to serializable.

Doctors still on call0
Ruleat least 1
Requests refused0

2 requests each saw 2 doctors on call, passed the check and committed on different rows. 0 remain on call but the rule needs 1: the invariant is broken and the database reported no error.

Snapshot isolation: every concurrent request passes its own check, so the remaining count is n − k (not below zero). Serializable: requests run as if one at a time, so the count stops at the minimum.

Where to go next