0Pricing
SQL Interview Prep · Lesson

Dirty, Non-Repeatable and Phantom Reads

The three read anomalies and which isolation level stops each.

Dirty, Non-Repeatable and Phantom Reads is a free SQL Interview Prep lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the SQL Interview Prep learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

The Three Read Anomalies

Isolation levels exist to prevent specific concurrency bugs called read anomalies. Interviewers expect you to define all three precisely and map each to the level that stops it.

  • Dirty read - reading uncommitted data
  • Non-repeatable read - a row changes between two reads
  • Phantom read - new rows appear between two reads

The trick is distinguishing non-repeatable from phantom, because both involve re-querying and getting different results.

Dirty Read: Definition

A dirty read happens when transaction T1 reads a row that transaction T2 has modified but not yet committed. If T2 then rolls back, T1 has acted on data that never truly existed.

Only READ UNCOMMITTED permits dirty reads. Every higher level forbids them.

Real-world danger: approving a loan based on a deposit that gets rolled back seconds later.

Dirty Read: Timeline

Read the two columns as a timeline. T1 is running at READ UNCOMMITTED.

T1 sees balance 700, but T2 never commits. The 700 was a phantom of T2's in-progress work. After T2 rolls back, the real value is still 500. T1 made a decision on garbage data.

-- T2 (not committed)        | -- T1 (READ UNCOMMITTED)
BEGIN;                       |
UPDATE accounts              |
  SET balance = 700          |
  WHERE id = 1;              |
                             | SELECT balance FROM accounts
                             |   WHERE id = 1;  -- reads 700 (dirty!)
ROLLBACK;                    |
                             | -- T1 acted on a value that never existed

Non-Repeatable Read: Definition

A non-repeatable read happens when T1 reads a row, T2 commits an update or delete to that same row, and T1 reads it again and sees a different value.

Note the key difference from a dirty read: here T2 has committed. The data is real, but it changed underneath T1 within a single transaction.

READ COMMITTED still allows this. REPEATABLE READ and higher prevent it by reading from a stable snapshot.

Non-Repeatable Read: Timeline

T1 runs at READ COMMITTED and reads the same row twice. Between the reads, T2 commits a change.

The same primary key returns two different values inside one transaction. That inconsistency can break multi-step logic that assumes the row is stable.

-- T1 (READ COMMITTED)              | -- T2
BEGIN;                              |
SELECT balance FROM accounts        |
  WHERE id = 1;  -- 500            |
                                    | BEGIN;
                                    | UPDATE accounts SET balance = 900
                                    |   WHERE id = 1;
                                    | COMMIT;
SELECT balance FROM accounts        |
  WHERE id = 1;  -- 900 (changed!) |
COMMIT;                             |

Phantom Read: Definition

A phantom read happens when T1 runs a query with a search condition, T2 commits an INSERT (or DELETE) of rows matching that condition, and T1 re-runs the query and sees a different set of rows.

The distinction from a non-repeatable read: a non-repeatable read is about an existing row's value changing; a phantom is about the number of rows matching a predicate changing.

Only SERIALIZABLE is guaranteed by the standard to prevent phantoms.

Phantom Read: Timeline

T1 counts high-value accounts twice. Between the counts, T2 inserts a new qualifying row and commits.

No existing row changed, yet the COUNT differs. The new row is the "phantom" that appeared in T1's result set.

-- T1 (REPEATABLE READ, standard)      | -- T2
BEGIN;                                 |
SELECT COUNT(*) FROM accounts           |
  WHERE balance > 1000;  -- 3          |
                                       | INSERT INTO accounts(id, balance)
                                       |   VALUES (99, 5000);
                                       | COMMIT;
SELECT COUNT(*) FROM accounts           |
  WHERE balance > 1000;  -- 4 (phantom)|
COMMIT;                                |

Mapping Anomalies to Levels

This mapping is the heart of the topic. The lowest level that prevents each anomaly:

  • Dirty read prevented from READ COMMITTED upward.
  • Non-repeatable read prevented from REPEATABLE READ upward.
  • Phantom read prevented from SERIALIZABLE (per the standard).

Notice the names line up: REPEATABLE READ makes reads repeatable; the levels are named after the anomaly they newly fix.

Non-Repeatable vs Phantom: The Sharp Line

The most common interview mix-up. Hold onto this one sentence:

Non-repeatable read = an existing row's value changed. Phantom read = the set of matching rows changed (rows added or removed).

Test yourself: T2 runs UPDATE ... WHERE id = 5 then commits, and T1 re-reads row 5. That is non-repeatable. T2 runs INSERT of a new row matching T1's WHERE, and T1 re-runs the query. That is a phantom.

Write Skew: The Bonus Anomaly

Senior interviews may push past the three standard anomalies to write skew: two transactions each read an overlapping set, make disjoint writes based on what they read, and both commit, leaving a state neither would have allowed alone.

Classic example: two doctors are on call; each checks that another doctor is on call and then takes themselves off shift. Both succeed, leaving zero coverage.

Snapshot isolation (Postgres REPEATABLE READ) permits write skew; only SERIALIZABLE stops it. Mentioning this signals depth.

Lost Update: The Fourth Trap

Interviewers sometimes slip in lost update, which is not in the standard's anomaly list but appears constantly in practice. Two transactions read the same value, both compute a new value from it, and both write back. The second write silently overwrites the first.

Example: two transfers each read balance 500, each subtract an amount, and each write their result. One subtraction is lost.

The fix is not just a higher isolation level but explicit locking with SELECT ... FOR UPDATE, or an atomic update that computes in the database rather than in the application.

-- Safe pattern: lock the row, or compute atomically
BEGIN;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;  -- locks row
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;
-- Or simply: UPDATE accounts SET balance = balance - 100 WHERE id = 1;

Quick Check

Identify the anomaly from its behavior.

Recap: Anomalies and Their Cures

Three read anomalies, each cured by a higher isolation level:

  • Dirty read (uncommitted data) - cured at READ COMMITTED.
  • Non-repeatable read (existing row value changes) - cured at REPEATABLE READ.
  • Phantom read (matching row set changes) - cured at SERIALIZABLE.

Keep the sharp line between non-repeatable and phantom, and drop write skew if the interviewer wants more. Next we look at how engines actually enforce isolation: locking, deadlocks, and MVCC.

Frequently asked questions

Is the “Dirty, Non-Repeatable and Phantom Reads” lesson free?

Yes — the full text of “Dirty, Non-Repeatable and Phantom Reads” is free to read here on the web, and the SQL Interview Prep course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the SQL Interview Prep course, upgrade to CoddyKit PRO.

What will I learn in “Dirty, Non-Repeatable and Phantom Reads”?

The three read anomalies and which isolation level stops each. You practise SQL Interview Prep with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start SQL Interview Prep?

No prior experience is required. SQL Interview Prep on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Dirty, Non-Repeatable and Phantom Reads” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this SQL Interview Prep lesson?

Yes. Every SQL Interview Prep lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. ACID Properties Explained
  2. The Four Isolation Levels
  3. Dirty, Non-Repeatable and Phantom Reads
  4. Deadlocks, Locking and MVCC
← Back to SQL Interview Prep