Beyond Conflicts: How Isolation Levels Tighten the Noose Around Concurrency

This blog post is another take at isolation levels, a topic I first explored in one of my earlier posts. The motivation this time is to relook at isolation levels from a different perspective so that isolation levels feel understood rather than merely studied.

In a previous post, we saw how databases detect and handle conflicts to preserve serializability. But here's the thing! Even strongest isolation levels can't wish conflicts away. A reader may inevitably run into a writer that later changes what was read. Different concurrency-control mechanisms handle such tensions in different ways at different isolation levels.

Crucially, every isolation level enforces a minimum degree of order i.e., conflicting operations aren't reordered (e.g., an RW dependency does not quietly become WR), because that would change semantics and make even basic reads and writes meaningless. What weaker levels do is delay or ignore some implied ordering constraints across statements or transactions, and that omission lets inconsistent states slip through. Seen this way, isolation levels are ways of loosening or tightening the noose, deciding how much concurrency we allow before anomalies start to appear.

Isolation Level Minimum Order Enforcement
Read Uncommitted Prevents total chaos: at least maintains sequence of operations inside a transaction
Read Committed Enforces commit-time visibility: readers never see uncommitted writes
Repeatable Read / SI Enforces snapshot consistency: readers see a stable historical order of commits
Serializable Enforces full conflict order: all RW/WW edges respected, graph kept acyclic

Let’s unwind this perspective, strand by strand, to see how isolation levels really emerge from the push and pull of conflicts.

Watching the Noose Tighten

So far, we’ve explored how conflicts are inevitable and every isolation level must deal with them, but not all choose to restrain them equally. Now it’s time to see that restraint in action.

Each isolation level tightens the rope a little more around conflicting operations, deciding when to intervene and how much order to preserve.
At one extreme, the system lets transactions run wild, barely enforcing any rules. At the other, it holds every dependency in place, ensuring a perfect serial order but at the cost of concurrency.

Let’s trace this tightening grip level by level, from a world of complete freedom to one of total control.

1. Read Uncommitted: the Noose Is Off

The system neither blocks nor validates anything.

Anomaly What Happens Why the Issue Appears
Dirty Read T1 reads data written but not committed by T2. The RW dependency exists, but the database doesn’t enforce commit order - the reader can see uncommitted versions.
Non-Repeatable Read T1 re-reads a row after T2 commits a new value. Each statement runs in its own visibility window; the database re-evaluates snapshots per statement, so prior RW dependencies aren’t remembered.
Phantom Read T1 re-runs a range query; T2 adds a new matching row. The system doesn’t track predicate-level RW dependencies, so changes to qualifying rows aren’t isolated.
Dirty Write Prevented - uncommitted writes blocked. Universally prevented - WW dependencies are always serialised or blocked to preserve recoverability.
Lost Update T1 reads X=100; T2 writes X=200; T1 writes X=110 -> T₂’s update lost. The database fails to validate stale reads - it doesn’t check whether the value read by T1 was changed by another transaction before the write.
Write Skew T1 and T2 each read disjoint rows and update their own. Each transaction’s reads are consistent, but the system doesn’t detect cross-item RW<->RW dependency cycles, allowing logically inconsistent outcomes.

Everything that can go wrong does. The rope lies slack on the floor.

2. Read Committed: The First Tightening

Only committed data may be read or overwritten.

Anomaly What Happens Why the Issue Appears
Dirty Read Prevented - only committed data visible. RW dependencies are checked at commit boundaries, so readers never see uncommitted versions.
Non-Repeatable Read T1 reads the same row twice and sees a new value after T2 commits. Each statement runs with its own visibility window; the system doesn’t preserve prior RW relationships between statements.
Phantom Read T1 re-runs a predicate query and sees new rows inserted by T2. The database doesn’t maintain range-level or predicate-level read dependencies across statements.
Dirty Write Prevented - uncommitted writes blocked. WW dependencies are serialised or deferred until the earlier writer commits or aborts.
Lost Update T1 and T2 both read same data then write; last writer wins. The system doesn’t validate RW overlap between statements - stale reads go undetected.
Write Skew T1 and T2 each read each other’s data before writing. The database enforces order per item but not cross-item RW<->RW dependency cycles, allowing invariants to break.

Dirty operations are gone, but RW conflicts still slip between statements. The noose tightens, just a little.

3. Repeatable Read / Snapshot Isolation: Steady Sight

Each transaction sees a consistent snapshot from start to finish.

Anomaly What Happens Why the Issue Appears
Dirty Read Prevented - reads use committed snapshot. RW conflicts are restricted to committed versions only; uncommitted RW edges are never followed.
Non-Repeatable Read Prevented - same snapshot throughout transaction. RW dependencies are anchored to a fixed commit state; subsequent WW changes remain invisible.
Phantom Read T1 re-runs predicate; T2 adds a new matching row. Predicate-level RW dependencies aren’t tracked; new rows written by T2 (WW on table range) can slip into T1’s result.
Dirty Write Prevented — writers serialize on commit. WW dependencies are serialised at commit; overlapping uncommitted writes aren’t permitted.
Lost Update T1 and T2 write same row based on stale reads -> one aborts or both commit inconsistently (depends on implementation). Direct WW conflicts are checked, but RW -> WW overlaps (stale read before write) may escape detection.
Write Skew T1 and T2 read disjoint rows then update -> invariant breaks. Two RW edges form a logical cycle across disjoint items; since no direct WW conflict exists, the system doesn’t detect it.

RW edges are now tamed by snapshots; WW edges handled at commit.
Only predicate-level phantoms and cross-row write skews remain.

4. Serializable: The Final Knot

All RW and WW edges are tracked; any cycle causes an abort.

Anomaly What Happens Why the Issue Appears
Dirty Read Prevented. All RW dependencies are enforced; no transaction can read from an uncommitted or out-of-order writer.
Non-Repeatable Read Prevented. RW dependencies are fully tracked; any violation of consistent read order causes rollback.
Phantom Read Prevented. Range and predicate RW dependencies are recorded; cycles involving new inserts are detected and aborted.
Dirty Write Prevented. WW dependencies are strictly serialised; overlapping or concurrent uncommitted writes are never allowed.
Lost Update Detected via RW + WW dependency cycle -> one aborted. RW + WW dependency cycles (read-stale -> overwrite) are observed in the global dependency graph; one transaction is rolled back to preserve order.
Write Skew Two-way RW cycle detected -> one aborted. Two-way RW cycles (mutual reads of future writes) are recognised; system ensures the dependency graph remains acyclic.

The rope is taut, every motion serialised. No anomalies survive.

Summary: Isolation Levels and Anomalies Prevented

Isolation Level Dirty Read Non-repeatable Read Phantom Read Lost Update Dirty Write Write Skew
Read Uncommitted Allowed Allowed Allowed Allowed Prevented Allowed
Read Committed Prevented Allowed Allowed Allowed Prevented Allowed
Repeatable Read / Snapshot Isolation Prevented Prevented Allowed Prevented Prevented Allowed
Serializable Prevented Prevented Prevented Prevented Prevented Prevented

Even Read Uncommitted databases forbid dirty writes to preserve recovery correctness.

Conclusion

Conflicts are the raw material of concurrency. They can’t be eliminated, only managed. Every isolation level decides how soon and how strictly to intervene when transactions collide.

At the loosest end, systems allow conflicts to play out freely, hoping they don’t do much harm. At the strictest end, they watch every dependency, ready to abort anything that dares to break order. Between these poles lies the entire spectrum of database behaviour i.e., performance traded for predictability, throughput for trust.

The job of an isolation level, then, isn’t to erase conflicts but to tame their consequences i.e., to decide when a read should wait, when a write should roll back, and when both can safely coexist.

Seen through this lens, isolation levels aren’t arbitrary modes on a database dial. They are degrees of restraint, tightening the noose one notch at a time i.e., from Read Uncommitted, where almost everything goes,
to Serializable, where every interleaving is examined and held accountable.

Isolation Level When the Database Intervenes Guarantee Provided
Read Uncommitted Almost never — only forbids dirty writes Prevents total chaos but allows inconsistency
Read Committed After each commit boundary Prevents dirty reads and dirty writes
Repeatable Read / Snapshot Isolation At transaction boundaries Preserves a consistent view within each transaction
Serializable Continuously — detects or blocks dependency cycles Guarantees full serial order

In the end, serialisability isn’t about avoiding conflicts at all. It’s about orchestrating them so perfectly that the world still appears calm. Databases don’t remove disorder; they choreograph it. And isolation levels decide how tightly the dance floor is roped off.