ποΈIsolation levels
Concurrency is a dial, not a switch
Lower isolation lets more transactions overlap, higher throughput, more anomalies. SERIALIZABLE gives the strongest illusion of running alone, but you pay in retries, deadlocks, and latency variance (watch those p99 tails).
πAnomaly matrix (ANSI picture)
Engines add nuance, treat this table as the baseline story, then read vendor notes.
| Level | Dirty read | Non-repeatable read | Phantom read |
|---|---|---|---|
| READ UNCOMMITTED | Possible* | Yes | Yes |
| READ COMMITTED | No | Yes | Yes |
| REPEATABLE READ | No | No | Maybe** |
| SERIALIZABLE | No | No | No |
β‘ Advanced / dialect note
Isolation mismatch
- Assuming READ COMMITTED prevents all lost updates without explicit locking or optimistic versioning.
- Long analytics on the primary under SERIALIZABLE defaults, blocking OLTP and widening p99.
- ORM-managed scopes fighting hand-written SQL in the same request, nested or leaked transactions.
Explicit levels and observability
- Map invariants to anomaly classes; set isolation per transaction where engines allow.
- Budget retries for serialization failures; send heavy reads to replicas with known staleness.
- Watch lock wait time and transaction age, not only CPU.
π₯MVCC: The Engine Under the Dial
How PostgreSQL and InnoDB provide isolation without heavy locking.
XMIN, XMAX, and Snapshots
In MVCC, every row has invisible headers: XMIN (ID of the transaction that created the row) and XMAX (ID of the transaction that deleted/updated it). When you start a query, you get a Snapshot of all active Transaction IDs. You can only see a row if its XMIN is committed and XMAX is either 0 or uncommitted.
Reads Never Block Writes
Write Skew
The MVCC Tax: VACUUM
Since updates create new versions, the physical file accumulates "Dead Tuples." A background process (VACUUM) must eventually reclaim this space. If you have long-running transactions, they "pin" old versions, preventing VACUUM and causing Table Bloat.
Set isolation per transaction (PostgreSQL)
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT balance FROM wallets WHERE user_id = 9;
COMMIT;| balance |
|---|
| 150 |
Operator mindset
When users report βghost numbers that change on refresh,β capture pg_stat_activity, transaction age, and isolation level, half the time it is expected RC behavior, not corruption.
TL;DR
Isolation levels trade concurrency for anomaly prevention: READ UNCOMMITTED (dirty reads), READ COMMITTED (default in many engines, no dirty reads), REPEATABLE READ, and SERIALIZABLE (strongest). MVCC engines implement levels differently than lock-based textbooks, verify behavior for phantoms and serialization failures on your database. Pick the weakest level that still matches product guarantees.