4.4.18 · D4Databases

Exercises — Concurrency anomalies — dirty read, non-repeatable read, phantom read

3,021 words14 min readBack to topic

Before we start, one picture fixes the vocabulary we will reuse in every solution.

Figure — Concurrency anomalies — dirty read, non-repeatable read, phantom read

Level 1 — Recognition

Here you only decide which anomaly (if any) a scenario shows. No isolation levels yet.

L1.1 — Name the anomaly (rollback)

Recall Solution

read a value (qty = 5) that had written but not committed, and then undid it. 's read is based on data that never officially existed. Answer: dirty read. The give-away is the ROLLBACK — only a dirty read can be invalidated by an abort, because only a dirty read looks at uncommitted data.

L1.2 — Name the anomaly (same row, two values)

Recall Solution

Both of 's reads see committed data, so it is not dirty. But the same row (id 7) gave two different values because committed an UPDATE in between. Answer: non-repeatable read.

L1.3 — Name the anomaly (result set grew)

Recall Solution

No existing row changed value; a new row entered the city='Pune' range. The membership of the result set changed. Answer: phantom read.

L1.4 — Name the anomaly (trick)

Recall Solution

None. Nothing else touched the data between the two reads, so both reads agree. An anomaly requires another transaction's interference — here there is no . This is a normal, consistent read.


Level 2 — Application

Now apply the anomaly→isolation-level rules from the parent's matrix.

L2.1 — Minimum level to stop a dirty read

Recall Solution

Dirty reads = reading uncommitted data. The lowest level that forbids reading uncommitted data is Read Committed. Answer: Read Committed. (Read Uncommitted still allows it; Repeatable Read and Serializable also work but are stronger than needed — the question asks for the lowest.)

L2.2 — Which anomalies remain at Read Committed?

Recall Solution

From the matrix: Read Committed prevents dirty reads (✓) but allows non-repeatable (✗) and phantom (✗). Answer: non-repeatable read and phantom read can still occur.

L2.3 — Cheapest level for a stable re-read of one row

Recall Solution

Same row, must be identical on re-read ⇒ we must forbid non-repeatable reads. The lowest level that does so is Repeatable Read. Since there is no range query, phantoms are irrelevant, so Serializable would be overkill. Answer: Repeatable Read.

L2.4 — Count anomalies allowed per level

Recall Solution

Read the matrix rows and count the ✗ (allowed):

  • Read Uncommitted: dirty ✗, non-rep ✗, phantom ✗ → 3 allowed
  • Read Committed: non-rep ✗, phantom ✗ → 2 allowed
  • Repeatable Read: phantom ✗ (per SQL standard) → 1 allowed
  • Serializable: none → 0 allowed Answer: 3, 2, 1, 0.

Level 3 — Analysis

Now reason about specific interleavings and locking behaviour.

Figure — Concurrency anomalies — dirty read, non-repeatable read, phantom read

L3.1 — Classify each line of an interleaving

Recall Solution

Under Read Uncommitted, reads see the latest written value, committed or not.

  • t3 reads 200. At this point 200 is uncommitted → this is a dirty read.
  • t5 reads 200. Now 200 is committed (t4). Compared with t1's 100 for the same row, the value changed across reads → this is a non-repeatable read. Answer: t3 = 200 (dirty), t5 = 200 (non-repeatable vs the t1 read of 100).

L3.2 — Why row locks don't stop phantoms

Recall Solution

's locks are on rows that already existed when it read. A brand-new row was never locked — you cannot lock a row that does not exist yet. So the INSERT succeeds, and 's second range query returns the extra row: a phantom. To stop it you must lock the predicate / range itself (e.g. a range or "next-key" lock on dept='Sales'), which blocks any insert into that range. That is what Serializable does. Answer: row locks cover existing rows only; a predicate/range lock is required to block phantom inserts.

L3.3 — Does MVCC change the story?

Recall Solution

Under snapshot isolation keeps reading from its frozen snapshot taken at start. It sees the old version of the row, not 's new committed value. Both reads return the same value. Answer: no non-repeatable read — MVCC serves the old version, so the two reads agree. (This is why many engines' "Repeatable Read" is implemented with MVCC rather than long-held read locks.)

L3.4 — Spot the deadlock risk

Recall Solution

holds X and waits for Y; holds Y and waits for X — a circular wait. This is a deadlock. Stricter isolation (more/longer locks) increases the chance of deadlocks, so the engine must detect and abort one victim. Answer: a deadlock; higher isolation levels make deadlocks more likely, which is part of their performance cost.


Level 4 — Synthesis

Design and justify — combine multiple ideas.

L4.1 — Pick the level for a banking transfer

Recall Solution

The danger: reads balance = 100, also reads 100, both think "enough for 100", both debit → account goes to –100. The read of 100 became stale after the other's committed write: this is a non-repeatable-read–class hazard on the decision row. The lowest level guaranteeing the read stays valid until commit is Repeatable Read (or equivalently take a SELECT ... FOR UPDATE write-lock). No range query is involved, so Serializable is not required. Answer: Repeatable Read (or explicit row write-lock).

L4.2 — Pick the level for an inventory audit

Recall Solution

New matching rows can appear between the two range queries → phantom hazard. Only Serializable (predicate/range locking or serialization checks) forbids phantoms per the SQL standard. Answer: Serializable.

L4.3 — Cheapest level satisfying a set of requirements

Recall Solution
  • (a) needs at least Read Committed.
  • (b) needs at least Repeatable Read (kills non-repeatable reads).
  • (c) there are no range queries, so phantoms cannot arise — Serializable is unnecessary. The lowest level meeting (a) and (b) is Repeatable Read, and it trivially satisfies (c). Answer: Repeatable Read.

Level 5 — Mastery

Multi-part, subtle — the full toolbox at once.

L5.1 — Build the full matrix from scratch

Recall Solution
Level Dirty Non-Repeatable Phantom
Read Uncommitted
Read Committed
Repeatable Read
Serializable

Generating rule: each step down the ladder adds a ✓ from left to right — first stop reading uncommitted (dirty), then freeze rows you read (non-repeatable), then freeze the predicate/range (phantom). The ✓ region grows monotonically down and to the right.

L5.2 — Match mechanism to anomaly

Recall Solution
  • (iv) reading only committed versions → removes dirty read.
  • (ii) hold read-lock on read rows until commit → removes non-repeatable read (those rows can't be updated).
  • (iii) predicate/next-key lock → removes phantom read (no inserts into the range).
  • (i) release read-lock immediately → this is Read Committed behaviour: removes dirty reads but still allows non-repeatable and phantom. Answer: iv→dirty, ii→non-repeatable, iii→phantom, i→(Read Committed: dirty only).

L5.3 — Trace an interleaving and count anomalies

Recall Solution

Under Read Committed, uncommitted writes are invisible.

  • t3: the t2 write is uncommitted → invisible → reads 100 (unchanged). No dirty read (Read Committed forbids it). And the value equals the t1 value, so no non-repeatable read yet.
  • t5: counts rows with bal=100. Say there is 1 such row (id 1) → COUNT = 1.
  • t6: inserts a new bal=100 row and commits.
  • t7: same range query now returns COUNT = 2 — a phantom read (a new matching row appeared). So exactly 1 anomaly: a phantom read. The dirty read was blocked by Read Committed, and no committed UPDATE hit an existing row re-read, so there is no non-repeatable read. Answer: 1 anomaly (phantom). (Values: t3 = 100, t5 = 1, t7 = 2.)

L5.4 — Severity ordering and the mnemonic

Recall Solution

Weakest cure → strongest: Dirty (Read Committed) → Non-repeatable (Repeatable Read) → Phantom (Serializable). Mnemonic DNP / RRS: Dirty, Non-repeatable, Phantom fixed by Read Committed, Repeatable Read, Serializable.


Recall Final self-check (answers hidden)

Fastest cure for a dirty read? ::: Read Committed Cure that stops non-repeatable but standard-allows phantom? ::: Repeatable Read Only standard level with zero anomalies? ::: Serializable Why don't row locks stop phantoms? ::: The new row didn't exist to be locked; you need a predicate/range lock Does MVCC snapshot isolation give non-repeatable reads on re-read? ::: No — it serves the old snapshot version