Chapter 18: Isolation Levels — Read Committed to Serializable

Full isolation (every transaction sees a perfect snapshot) is expensive. Databases offer levels of isolation — trade correctness for performance.

The Concurrency Problems

ProblemDescriptionExample
Dirty ReadRead uncommitted data from another transactionSee a transfer that gets rolled back
Non-Repeatable ReadSame query returns different results within one transactionRead balance=1000, read again=900 (someone committed between)
Phantom ReadNew rows appear that match a previous query's WHERECOUNT(*) returns 10, then 11 (someone inserted)
Lost UpdateTwo transactions read-modify-write, one overwrites the otherBoth read balance=1000, both write 900. Should be 800.

The Four Isolation Levels (SQL Standard)

LevelDirty ReadNon-RepeatablePhantomPerformance
Read UncommittedPossiblePossiblePossibleFastest
Read CommittedPreventedPossiblePossibleFast (PostgreSQL default)
Repeatable ReadPreventedPreventedPossible*Medium (MySQL default)
SerializablePreventedPreventedPreventedSlowest

Read Committed (PostgreSQL Default)

Each statement sees only data committed before that statement started. Different statements in the same transaction can see different snapshots.

-- Transaction A (Read Committed)
BEGIN;
SELECT balance FROM accounts WHERE id=1;  → 1000
-- Transaction B commits: UPDATE balance=900 WHERE id=1
SELECT balance FROM accounts WHERE id=1;  → 900 (sees B's commit!)
COMMIT;

Repeatable Read

The transaction sees a snapshot from its start. All queries see the same data, regardless of other commits.

-- Transaction A (Repeatable Read)
BEGIN;
SELECT balance FROM accounts WHERE id=1;  → 1000
-- Transaction B commits: UPDATE balance=900 WHERE id=1
SELECT balance FROM accounts WHERE id=1;  → 1000 (still sees old snapshot!)
COMMIT;

Serializable

Transactions behave as if they ran one after another (serial order). The database detects conflicts and aborts one transaction.

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
-- If a conflict is detected:
-- ERROR: could not serialize access due to concurrent update
-- Your app must RETRY the transaction
💡 Practical Advice

Setting Isolation Level

-- Per transaction
BEGIN ISOLATION LEVEL REPEATABLE READ;

-- Per session
SET default_transaction_isolation = 'serializable';

-- Globally (postgresql.conf)
default_transaction_isolation = 'read committed'
Key Takeaways