Reference · Transactions

Transaction Isolation — Cheat Sheet

Pairs with Lesson 2: Isolation Levels and Check-Then-Act Races.

Glossary

TermMeaning
READ COMMITTEDPostgres default. Each statement sees data committed as of when that statement starts. No row locks on plain reads.
REPEATABLE READWhole transaction sees a consistent snapshot from when the transaction started. Prevents non-repeatable reads, still allows some anomalies (e.g. write skew).
SERIALIZABLEStrongest level — behaves as if transactions ran one-at-a-time. Detects conflicts and aborts one transaction with a serialization-failure error; caller must retry.
Check-then-act raceReading a value, deciding in application code, then writing — with no guarantee nothing else changed the value in between.
Lost updateTwo transactions read the same value, both compute a new value from it, second write silently overwrites the first's intent.
Write skewTwo transactions read overlapping data, each writes to a different row, but the combination violates an invariant neither transaction alone would violate. Not caught by REPEATABLE READ — needs SERIALIZABLE or explicit locking.
Row lockTaken automatically by UPDATE/DELETE on the specific row touched, at the moment of the write — not on plain SELECT.

Fix pattern: atomic conditional write

Rule of thumb

If a decision depends on current data, put the condition in the UPDATE's WHERE clause — don't read, decide in app code, then write separately.

UPDATE t SET col = col - n WHERE id = ? AND col >= n;

Check rows-affected: 0 means the condition failed against current data, not stale app-side data.

When atomic UPDATE isn't enough

Write skew (invariant spans multiple rows, e.g. "at least one doctor on call") needs either SELECT ... FOR UPDATE to lock the rows read, or SERIALIZABLE isolation with retry logic on serialization failure.

Primary sources