Transaction Isolation Levels — Deep Dive

Dec 30, 2024

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.

ilia-kritiuk.dev