Why This Matters
Databases serve many users at once. While you are reading a bank balance, someone else might be transferring money into your account. While an inventory system checks stock, another process is selling the last item. What data do you see? The old value? The new value? A mix of both? The answer depends on the isolation level.
Isolation levels control the tradeoff between correctness and performance in concurrent systems. Read Committed is the default in most databases and offers a reasonable balance. Serializable provides the strongest guarantees but is slower. Choosing the wrong level leads to subtle, hard-to-reproduce bugs -- the kind that only appear under heavy load in production.
Define Terms
Visual Model
The full process at a glance. Click Start tour to walk through each step.
Higher isolation levels prevent more anomalies but reduce concurrency. Most apps use Read Committed.
Code Example
-- Setting isolation level in SQL (PostgreSQL syntax)
-- Read Committed (default in PostgreSQL)
BEGIN;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT balance FROM accounts WHERE id = 1; -- sees committed data
COMMIT;
-- Repeatable Read
BEGIN;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT balance FROM accounts WHERE id = 1; -- snapshot taken here
-- Even if another transaction changes this row and commits,
-- this transaction will still see the original value
SELECT balance FROM accounts WHERE id = 1; -- same result guaranteed
COMMIT;
-- Serializable (strictest)
BEGIN;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SELECT SUM(balance) FROM accounts; -- sees a consistent snapshot
-- If another transaction modifies accounts concurrently,
-- one of them will be aborted with a serialization error
COMMIT;
-- Demonstrating a non-repeatable read
-- Terminal 1: Terminal 2:
-- BEGIN;
-- SELECT balance FROM accounts
-- WHERE id = 1;
-- -- Returns: 1000 BEGIN;
-- UPDATE accounts SET balance = 500
-- WHERE id = 1;
-- COMMIT;
-- SELECT balance FROM accounts
-- WHERE id = 1;
-- -- Returns: 500 (changed!)
-- -- This is a non-repeatable read
-- COMMIT;Interactive Experiment
Try these exercises:
- Open two database connections. In connection A, start a transaction and UPDATE a row without committing. In connection B, try to SELECT that row at Read Committed level. What do you see?
- Repeat the experiment at Read Uncommitted level (if your database supports it). Can B see A's uncommitted change?
- Demonstrate a non-repeatable read: have A read a value, B update and commit it, then A read again.
- Try Serializable: have two transactions both read and update the same row. Does one get aborted?
Quick Quiz
Coding Challenge
Write a class `IsolationSimulator` that simulates two isolation levels. It has a shared `data` store and tracks transactions. At 'read-uncommitted' level, `read()` returns the latest written value even if uncommitted. At 'read-committed' level, `read()` only returns committed values. Implement `begin(level)`, `write(key, value)`, `read(key)`, `commit()`, and `rollback()`.
Real-World Usage
Isolation levels affect every concurrent application:
- Web applications: Most use Read Committed by default. Each HTTP request sees a consistent snapshot of committed data.
- Financial systems: Bank transfers and payment processing often use Serializable to prevent double-spending and balance inconsistencies.
- Inventory management: Serializable or explicit locking prevents selling the last item twice when two customers check out simultaneously.
- Reporting queries: Long-running analytics queries use Repeatable Read or snapshot isolation to avoid seeing partial updates while scanning millions of rows.
- Optimistic concurrency: Instead of high isolation levels, many apps use version columns to detect concurrent modifications and retry on conflict.