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

ApproachIdeaCost
Atomic SQL updateUPDATE products SET stock = stock - 1 WHERE id = ? AND stock >= 1cheapest; only for simple counters
Optimistic lockingdetect the conflict at write time with a version column and retryno locks held; conflicts cause retries
Pessimistic lockinglock the row on read (SELECT ... FOR UPDATE) so others waitblocks 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.