Dirty Reads, Dirty Writes, Lost Updates
What This Concept Is
The three "obvious" concurrency anomalies. Each has a precise meaning and is prevented by a specific isolation level or mechanism. Memorize the shapes; you will recognize them in real bugs forever.
- Dirty read: transaction
T2reads a value thatT1wrote but has not yet committed. IfT1later aborts,T2has acted on data that never existed. - Dirty write: transaction
T2overwrites a value thatT1wrote but has not yet committed.T1's later abort might or might not undoT2's write correctly. - Lost update: two transactions read the same value, compute a new value from it, and both write back. One write overwrites the other silently. Classic read-modify-write race.
All three appear at the Read Uncommitted level. Dirty read and dirty write are prevented by Read Committed. Lost update is not prevented by Read Committed alone; it needs Snapshot Isolation with first-committer-wins, or SELECT ... FOR UPDATE, or explicit compare-and-set.
Why It Matters Here
Every team that has shipped "the counter is off by one" or "we charged the customer twice" has shipped a lost update. Dirty reads are rarer in modern databases because Read Committed is the usual default, but they show up in application-level caches and in message queues. Dirty writes are what a mutex is trying to prevent; if your DB permits them, any write-write invariant can break.
Concrete Example
Dirty Read
Accounts A = 500, B = 200. Two transactions:
time T1 (transfer 100 A->B) T2 (read total)
1 UPDATE A SET bal = bal - 100
2 SELECT SUM(bal) FROM accounts -> 600
3 UPDATE B SET bal = bal + 100
4 COMMIT
T2 saw A = 400, B = 200 mid-transfer, total 600. Correct total is 700. Dirty read if T2 did not require T1 to have committed.
Dirty Write
time T1 T2
1 UPDATE x SET val = 100
2 UPDATE x SET val = 200
3 ROLLBACK
If the system allows T2 to overwrite T1's uncommitted write, the rollback is ambiguous: does it restore the pre-T1 value (erasing T2) or leave T2's value alone? Most databases prevent this by row-level locking at any isolation level except Read Uncommitted on some engines.
Lost Update
Classic counter:
time T1 (increment by 1) T2 (increment by 1)
1 SELECT n FROM c WHERE id=1 -> 5
2 SELECT n FROM c WHERE id=1 -> 5
3 UPDATE c SET n = 5 + 1 WHERE id=1
4 UPDATE c SET n = 5 + 1 WHERE id=1
5 COMMIT
6 COMMIT
Both ran under Read Committed. Final n = 6, not 7. T1's update was lost because both read the same snapshot and neither saw the other's write until it was too late.
Common Confusion / Misconception
"Read Committed prevents lost updates." It does not. Read Committed prevents only dirty reads and dirty writes. Lost update happens between committed transactions reading the same pre-image.
"Use a transaction, it'll be safe." A BEGIN/COMMIT block at Read Committed wrapped around the naive read-modify-write above still loses updates. You need atomic UPDATE (SET n = n + 1), SELECT ... FOR UPDATE, Snapshot Isolation with first-writer-wins, or a compare-and-set pattern.
"Dirty writes don't happen in real systems because of locks." Only because most engines lock any write, regardless of isolation level. In engines that do not (or in application-level state), dirty writes are a common bug pattern.
How To Use It
For every read-modify-write path, name the concrete prevention:
- Atomic single statement:
UPDATE ... SET n = n + 1. Always preferred when expressible. - Explicit locking:
SELECT ... FOR UPDATEheld across the computation and write. - Compare-and-set:
UPDATE ... SET n = $new WHERE n = $read AND id = $idplus retry on zero rows affected. - Snapshot Isolation with first-committer-wins: PostgreSQL's REPEATABLE READ aborts one of the two and you retry.
If none of these is present, you have a lost-update bug waiting for load.
Check Yourself
- Why does wrapping a read-modify-write in
BEGIN; COMMIT;at Read Committed not prevent a lost update? - Name two distinct ways to turn a read-modify-write counter increment into a safe operation without raising the isolation level.
- A bank application under Read Committed reads two balances and returns their sum. Can the sum be wrong? Which anomaly permits that?
- Why is dirty write the rarest of the three in mainstream databases?
Mini Drill or Application
For each scenario, name the anomaly and at least one prevention:
- A like counter on a social feed sometimes shows
n - 1likes after two users like the same post simultaneously. - A balance-report page reports a total $100 lower than reality whenever a transfer is in flight, but only before a specific deployment rollback.
- An inventory system sold the last item twice because two checkouts both read quantity 1.
- A config-version field goes backwards from 7 to 6 when two admins edit it at once.
- A payment worker acted on a fraud-flag change that was later rolled back.