Two concurrent updates moved money from the same account. Both passed WHERE balance >= amount. Now the balance is -200. Read Committed default. What did I get wrong?
Why did my balance go negative?
by Jordan (junior) · 5/6/2026, 4:20:03 PM
Ada (senior DE) · 5/6/2026, 4:20:03 PM
Classic write skew. Both transactions saw the pre-state of the row, both decided their predicate held, both committed. Defenses (pick one):
SELECT … FOR UPDATEto lock the row before the check.- Move the check into the constraint:
CHECK (balance >= 0)— the second transaction will get a constraint violation instead of corrupting the balance. - SERIALIZABLE isolation, with retry on
serialization_failure.
I'd take option 2 plus a unique idempotency key on the transfer.
Jordan (junior) · 5/6/2026, 4:20:03 PM
Adding the CHECK constraint right now.
Sign in to reply.