Transaction Isolation

Transaction isolation controls how concurrent transactions can observe and interact with each other's changes. It provides rules that reduce concurrency anomalies while allowing databases to execute multiple transactions at the same time.

When several transactions run concurrently, their operations can overlap in time. Without isolation rules, one transaction might observe another transaction's intermediate or changing state. Isolation defines which interactions are permitted.

Common isolation levels are `Read Uncommitted`, `Read Committed`, `Repeatable Read`, and `Serializable`. They generally form a trade-off: stronger isolation restricts more concurrency but can reduce anomalies and may increase waiting or contention.

A `dirty read` occurs when a transaction reads data written by another transaction that has not committed. A `non-repeatable read` occurs when the same row is read twice and another committed transaction changes it between those reads. A `phantom read` occurs when repeating a predicate-based query produces a different set of matching rows because another transaction inserted, deleted, or otherwise changed qualifying rows.

In Java applications, isolation is usually configured through the database or JDBC/Spring transaction settings rather than implemented manually in application code. The exact behavior can depend on the database engine, so an interview answer should distinguish the SQL isolation-level definition from a particular database's implementation.

Key points

Interview questions

What is transaction isolation?

Transaction isolation defines how concurrent transactions are allowed to observe each other's changes. It determines whether a transaction can see uncommitted data and what happens when another transaction modifies data during its execution. Databases provide isolation levels to choose different consistency and concurrency trade-offs. The goal is to avoid unwanted concurrency anomalies while still permitting useful parallel work.

What is a dirty read?

A dirty read happens when one transaction reads data written by another transaction before that other transaction commits. If the writer later rolls back, the reader has observed a value that never became committed database state. `Read Uncommitted` permits this anomaly in the standard isolation model. Stronger isolation levels prevent it.

Can you explain the common transaction isolation levels?

`Read Uncommitted` permits the broadest visibility and can allow dirty reads. `Read Committed` prevents dirty reads but can allow later reads to observe committed changes made by other transactions. `Repeatable Read` provides stronger guarantees for repeated reads, while `Serializable` aims to produce behavior equivalent to transactions executing one at a time. The exact implementation and some anomaly behavior can vary between database systems.

What is the difference between a non-repeatable read and a phantom read?

A non-repeatable read concerns the value of data that was already read, where another committed transaction changes that data before the first transaction reads it again. A phantom read concerns a query that returns a set of rows based on a condition, where a later execution sees a different qualifying set. For example, an order query for `status = 'OPEN'` might return an additional matching row later. Database implementations can differ in exactly how these phenomena are prevented.

How would you choose an isolation level for a Java backend service?

I would first identify the application's consistency requirements and the anomalies that must be prevented. Then I would consider the database engine's actual implementation, workload, transaction duration, contention, and performance requirements. I would prefer the least restrictive level that satisfies the business invariant rather than automatically choosing the strongest level. For critical operations, I would also consider database constraints, locking, optimistic concurrency, and retry behavior instead of relying on isolation alone.