Careeroza
The Lost Update Problem
Two Buyers, One Last Phone
A web app handles many requests at the same time. When two transactions read the same row, change it in Java, and write it back, the second write silently overwrites the first. This is a lost update. Database transactions at the default isolation level (READ COMMITTED) do not stop it.
time Asha's transaction Ravi's transaction ──── ───────────────────────────────── ───────────────────────────────── t1 SELECT stock → 10 t2 SELECT stock → 10 t3 stock = 10 - 1; UPDATE stock=9 t4 COMMIT t5 stock = 10 - 2; UPDATE stock=8 t6 COMMIT ← Asha's change is lost
Reproduce It
Example
The same interleaving in plain Java. Three units were sold but stock dropped by only two:
public class Main {
static int stockInDb = 10;
public static void main(String[] args) {
int ashaRead = stockInDb; // t1
int raviRead = stockInDb; // t2
stockInDb = ashaRead - 1; // t3 Asha buys 1
System.out.println("after Asha: " + stockInDb);
stockInDb = raviRead - 2; // t5 Ravi buys 2
System.out.println("after Ravi: " + stockInDb);
System.out.println("units sold: 3, expected stock: 7, actual stock: " + stockInDb);
}
}Output
after Asha: 9 after Ravi: 8 units sold: 3, expected stock: 7, actual stock: 8
Three Ways to Fix It
| Approach | Idea | Cost |
|---|---|---|
| Atomic SQL update | UPDATE products SET stock = stock - 1 WHERE id = ? AND stock >= 1 | cheapest; only for simple counters |
| Optimistic locking | detect the conflict at write time with a version column and retry | no locks held; conflicts cause retries |
| Pessimistic locking | lock the row on read (SELECT ... FOR UPDATE) so others wait | blocks other transactions; risk of deadlocks |
Note: Raising the isolation level to SERIALIZABLE also prevents it, but it slows the whole database and causes many serialization failures. Locking just the rows you need is the usual answer.