Skip to content

The Four Isolation Problems#

When two transactions run concurrently without proper isolation, four specific things go wrong. Isolation levels are defined by which of these they prevent.

Problem 1 — Dirty Read#

Reading data from another transaction that hasn't committed yet — and might get rolled back.

-- Transaction B (transfer in progress, NOT committed yet)
UPDATE accounts SET balance = 1500 WHERE id = 123;  -- was 1000, adding 500
-- B has not committed yet

-- Transaction A reads mid-transfer
SELECT balance FROM accounts WHERE id = 123;
-- Returns: 1500  ← dirty read, B's write not committed

-- Transaction B fails → rolls back
UPDATE accounts SET balance = 1000 WHERE id = 123;  -- reverted

-- A saw $1500 that never actually existed

You made a decision based on money that was never real.


Problem 2 — Non-Repeatable Read#

Same query, same transaction, same row — different result because another transaction committed in between.

BEGIN TRANSACTION;  -- Transaction A (audit report)

SELECT balance FROM accounts WHERE id = 123;
-- Returns: 1000

-- Meanwhile Transaction B commits: balance = 1500

SELECT balance FROM accounts WHERE id = 123;
-- Returns: 1500  ← same query, same transaction, different result

COMMIT;

Within a single transaction, the world should look frozen.

For casual reads — fine, you refresh and see latest. For audit reports, financial calculations, multi-step operations — inconsistency within one transaction corrupts the result.

Non-Repeatable vs Dirty Read:

Dirty Read          → reading UNCOMMITTED data
Non-Repeatable Read → reading COMMITTED data, but it changed between reads


Problem 3 — Phantom Read#

Same query, same transaction — but new rows appear (or disappear) because another transaction inserted/deleted.

BEGIN TRANSACTION;  -- Transaction A (processing today's orders)

SELECT COUNT(*) FROM orders WHERE date = '2026-04-03';
-- Returns: 100

-- Meanwhile Transaction B inserts 2 new orders and commits

SELECT COUNT(*) FROM orders WHERE date = '2026-04-03';
-- Returns: 102  ← phantom rows appeared

COMMIT;

Like seeing ghosts — rows that weren't there before suddenly appear.

Phantom vs Non-Repeatable Read:

Non-Repeatable Read → same ROW changed its value
Phantom Read        → new ROWS appeared or existing rows disappeared


Problem 4 — Lost Update#

Two transactions read the same committed value, both compute an update based on it, both write — one overwrites the other.

The read is not stale — both transactions read the correct, committed value. The problem is a write-write conflict: neither transaction knows the other is also in progress, both independently decide to write, and the second write silently stomps the first.

-- Both transactions read balance = 1000 (correct, committed value)

-- Transaction A: adds $500
SELECT balance FROM accounts WHERE id = 123;  -- gets 1000 ← correct
-- (gap — B also reads the same correct value)
UPDATE accounts SET balance = 1500 WHERE id = 123;  -- 1000 + 500

-- Transaction B: adds $200 (based on the 1000 it also correctly read)
UPDATE accounts SET balance = 1200 WHERE id = 123;  -- 1000 + 200 ← overwrites A

-- Correct result: 1000 + 500 + 200 = $1700
-- Actual result:  $1200  ← A's $500 deposit is gone

This also explains the hotel double-booking problem — both users read "room available" (correct, committed value), both decide to book, both write. Neither read was stale. The conflict is purely in the concurrent writes.

T1: read → room available = true  ← correct committed value
T2: read → room available = true  ← correct committed value
T1: write → available = false
T2: write → available = false     ← both succeed, double booking

Lost update is a write-write conflict, not a stale read problem.

Solved by pessimistic locking (SELECT FOR UPDATE) or SERIALIZABLE isolation.


Problem What happens Caused by
Dirty Read Read uncommitted data that gets rolled back No read isolation
Non-Repeatable Read Same row, different value within one transaction Another transaction committed between reads
Phantom Read New rows appear/disappear within one transaction Another transaction inserted/deleted between reads
Lost Update One write overwrites another's committed write Both transactions read the same correct value, both write — write-write conflict