Skip to content

Isolation

Concurrent transactions are isolated from one another — each behaves as if it were the only transaction running.

Read Anomalies

Isolation levels exist to prevent these problems:

  • Dirty read — reading data that another transaction hasn't committed yet (it might roll back)
  • Non-repeatable read — reading the same row twice in one transaction and getting different values because another transaction committed in between
  • Phantom read — running the same query twice and getting new rows because another transaction inserted them in between

Isolation Levels (least → most strict)

Read Uncommitted

Can see uncommitted changes from other transactions. Almost never used in practice.

TX1: UPDATE accounts SET balance = 0 WHERE id = 1;  -- not committed yet
TX2: SELECT balance FROM accounts WHERE id = 1;      -- reads 0 (dirty read)
TX1: ROLLBACK;                                        -- TX2 acted on data that never existed

Read Committed

Each statement sees a fresh snapshot of committed data. Default in PostgreSQL and Oracle. The most common approach!

TX1: SELECT balance FROM accounts WHERE id = 1;  -- returns 100
TX2: UPDATE accounts SET balance = 80 WHERE id = 1; COMMIT;
TX1: SELECT balance FROM accounts WHERE id = 1;  -- returns 80 (non-repeatable read)

Repeatable Read

The transaction gets a snapshot at the start — any row you read stays the same. Default in MySQL/InnoDB.

TX1: SELECT balance FROM accounts WHERE id = 1;  -- returns 100
TX2: UPDATE accounts SET balance = 80 WHERE id = 1; COMMIT;
TX1: SELECT balance FROM accounts WHERE id = 1;  -- still returns 100 (snapshot)

But new rows can still appear (phantom reads):

TX1: SELECT * FROM accounts WHERE branch = 'A';   -- returns 3 rows
TX2: INSERT INTO accounts (branch, balance) VALUES ('A', 50); COMMIT;
TX1: SELECT * FROM accounts WHERE branch = 'A';   -- returns 4 rows (phantom)

Serializable

Full isolation — the result is the same as if transactions ran one after another. Safest, slowest.

The database either acquires range locks (locking-based) or detects dependency cycles and aborts one transaction at commit time (MVCC).

Higher isolation = more locking/conflict detection = less throughput.

How it's implemented

  • Locking-based (e.g. SQL Server) — the database holds read/write locks for longer at higher levels
  • MVCC (e.g. PostgreSQL, MySQL) — each transaction sees a snapshot of the data (essentially optimistic locking at the engine level); still needs conflict detection at Serializable

In practice, most applications use Read Committed + explicit SELECT ... FOR UPDATE on the rows that need protection, rather than raising the entire isolation level — see distributed-systems/concurrency/locking.md.