Transaction Isolation Levels — Deep Dive
🧩 Concept
A transaction isolation level defines how concurrent transactions interact and what kinds of data anomalies are allowed.
It’s always a trade-off between data integrity and throughput:
Higher isolation = more consistency, but less concurrency and performance.
🔹 1. READ UNCOMMITTED — “Anything Goes”
Description: Minimal isolation, almost no locking.
Allows:
- Dirty reads
- Non-repeatable reads
- Phantom reads
- Lost updates
Usage: Almost never used in production. Suitable only for analytics or diagnostics where perfect accuracy isn’t critical.
Spring / JPA:
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
⚠️ Note
Most databases (e.g., PostgreSQL) still behave like READ COMMITTED internally — true dirty reads are rarely possible in practice.
🔹 2. READ COMMITTED — The Production Default
Description: The most common and balanced isolation level.
Prevents: Dirty reads Allows: Non-repeatable reads, phantom reads, lost updates
Each SELECT within a transaction reads only committed data.
If another transaction commits between two reads, you’ll see the updated value.
Real-world fact: Roughly 95% of production systems run at this level. Oracle and SQL Server use it by default.
Spring / JPA Example:
@Transactional(isolation = Isolation.READ_COMMITTED)
Example anomaly:
T1: SELECT balance FROM account WHERE id=1 → 100
T2: UPDATE account SET balance=50 WHERE id=1; COMMIT
T1: SELECT balance FROM account WHERE id=1 → 50
→ Same query, different results — this is a non-repeatable read.
🔹 3. REPEATABLE READ — Stable Rows, Shifting Ranges
Description: Ensures that rows you’ve already read cannot change during the transaction.
Prevents: Dirty reads, non-repeatable reads Allows: Phantom reads
Once a row is read, it’s effectively “frozen” for the transaction — any concurrent updates to that row will block or wait.
However, new rows that match the same WHERE condition may still appear later — these are known as phantoms.
Spring / JPA:
@Transactional(isolation = Isolation.REPEATABLE_READ)
Phantom example:
T1: SELECT * FROM orders WHERE amount > 1000; -- 5 rows
T2: INSERT INTO orders (amount) VALUES (2000); COMMIT;
T1: SELECT * FROM orders WHERE amount > 1000; -- 6 rows
Note: In MySQL (InnoDB), this is the default isolation level. InnoDB uses MVCC (Multi-Version Concurrency Control), which creates a consistent snapshot at the start of the transaction — instead of relying on physical row locks — to provide repeatable reads efficiently and with minimal contention.
🔹 4. SERIALIZABLE — The Fortress
Description: The strictest and safest isolation level.
Prevents all anomalies:
- Dirty reads
- Non-repeatable reads
- Phantom reads
- Lost updates
Transactions behave as if they were executed sequentially, even though they may actually run concurrently. Under the hood, databases enforce this through range locks or MVCC conflict detection, ensuring full serializability at the cost of concurrency and performance.
Spring / JPA:
@Transactional(isolation = Isolation.SERIALIZABLE)
Downside: Heavy locking, reduced concurrency, and potential deadlocks. Use this level only when strong, strict consistency is mandatory — such as in accounting, inventory, or financial transaction systems.
🧩 Isolation in Spring and JPA
In Spring, you can control transaction isolation directly via the @Transactional annotation:
@Transactional(isolation = Isolation.READ_COMMITTED)
public void processPayment() {
// business logic
}
If not specified, Spring uses the database default:
Database Default Isolation
Oracle, PostgreSQL READ_COMMITTED
MySQL (InnoDB) REPEATABLE_READ
SQL Server READ_COMMITTED
You can also set it globally in application.yml:
spring:
jpa:
properties:
hibernate.connection.isolation: 2 # READ_COMMITTED
🧠 Choosing the Right Level
Isolation Level Dirty Read Non-repeatable Read Phantom Performance Common Use
READ UNCOMMITTED ✅ ✅ ✅ 🚀 Reporting, diagnostics
READ COMMITTED ❌ ✅ ✅ ⚡ Most OLTP systems
REPEATABLE READ ❌ ❌ ✅ ⚖️ Financial calculations
SERIALIZABLE ❌ ❌ ❌ 🐢 Critical transactions (banking, auditing)
⚙️ Architectural Recommendations
Don’t raise isolation without reason — stricter isolation means more locks and slower performance.
Use optimistic locking (@Version in JPA) when you need consistency without blocking.
Know your database’s behavior — isolation levels differ between Oracle, PostgreSQL, MySQL, and SQL Server.
Monitor long-held locks and deadlocks — often signs of overly strict isolation or inefficient access patterns.
Design for eventual consistency in distributed systems — isolation levels apply only within a single database instance.