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 Read-Write (RW) and Write-Write (WW) conflicts to preserve serializability. But not every system chooses to hold the rope that tight. Some loosen their grip, allowing more concurrency at the cost of occasional anomalies. In fact, isolation levels are nothing but different ways of loosening that noose, deciding how much concurrency we allow before anomalies start to slip in.
Let’s unwind this perspective, strand by strand, to see how isolation levels really emerge from the push and pull of conflicts.
RW and WW Are the Primitive Edges While Anomalies Are the Patterns They Form
Every anomaly in the isolation spectrum is nothing but a different pattern built from RW and WW edges.
RW conflicts govern what a transaction sees. WW conflicts govern whose change finally lands. Relax either, and you invite a family of anomalies. However, every isolation anomaly (lost update, write skew, etc.) is not just caused by a single RW or WW conflict, but by how these edges connect in a transaction dependency graph.
| Conflict Type | What It Influences | Typical Anomalies |
|---|---|---|
| RW | What a transaction believes about the world | Dirty Read, Non-repeatable Read, Phantom Read, Write Skew |
| WW | Whose belief becomes truth | Dirty Write, Lost Update |
| RW + WW cycles | Interlocking reads and writes | Write Skew, Lost Update |
If you imagine each transaction as a node, these edges form a directed graph. When that graph contains a cycle, you get a serializability violation, another anomaly.
The point I am trying to make here is that the correlation is not 1-to-1, but foundational. RW and WW are the building blocks; anomalies are the structures built from them.
Let's explore this idea a bit more in detail:
1. RW Conflicts -> Read-side Anomalies (mostly)
RW conflicts are at the heart of read-related anomalies, because they involve one transaction reading something that another later writes.
| RW-Driven Anomaly | How the RW Conflict Manifests |
|---|---|
| Dirty Read | T1 reads uncommitted data that T2 wrote -> RW conflict with uncommitted writer |
| Non-repeatable Read | T1 reads a row; T2 writes a new value; T1 re-reads -> RW conflict changes what T1 sees |
| Phantom Read | T1 runs a range query; T2 inserts a new matching row -> predicate-level RW conflict |
| Write Skew | Both transactions read each other’s state before writing -> two RW edges forming a cycle |
So yes, read anomalies are largely symptoms of unrestrained RW dependencies, and tightening RW control (via locks, validation, or snapshots) progressively eliminates them.
2. WW Conflicts -> Write-side Anomalies (mostly)
WW conflicts arise when two writers contend for the same item. They are directly tied to write-anomalies like overwrites and lost updates.
| WW-Driven Anomaly | How the WW Conflict Manifests |
|---|---|
| Dirty Write | T1 and T2 both update the same row before either commits -> overlapping uncommitted writes |
| Lost Update | T1 writes after reading stale data; T2’s later write overwrites T1’s result |
| Write Skew | Indirectly involves WW logic, two writers modify different rows whose combination violates an invariant |
WW conflicts are often easier to detect. Databases can simply serialize writes to the same record. That’s why dirty writes are universally forbidden, and lost updates vanish once WW conflicts are properly managed.
3. Some Anomalies Require Both RW and WW Edges
Not all anomalies are purely read-side or write-side. Some emerge only when both RW and WW edges participate in the same dependency cycle i.e., when a transaction reads something another later writes (RW), and then overwrites or depends on that same data in return (WW or RW back).
These hybrid cycles are what make anomalies like Lost Update and Write Skew deceptively subtle.
Lost Update: RW<->RW Cycle, Exposed by WW Overwrite
Two transactions read the same old value before either writes, and then both attempt to update the same record.
| Step | Description | Conflict Type |
|---|---|---|
| 1 | Both transactions read the same old data (e.g., balance = 100) | RW - each reads data the other later modifies |
| 2 | Both decide to update that value | RW<->RW - mutual stale reads form a logical cycle |
| 3 | One write overwrites the other’s result | WW - the lost update becomes visible |
The cycle arises from two RW dependencies, each transaction made decisions on values the other later changed. The WW overwrite doesn’t cause the cycle, it merely exposes the consequence: one update silently disappears.
Write Skew: Mutual RW<->RW Cycle
Two transactions read disjoint rows and then update them in a way that violates a shared invariant.
| Step | Description | Conflict Type |
|---|---|---|
| 1 | T1 reads a value that T2 will later modify | RW |
| 2 | T2 reads a value that T1 will later modify | RW |
| 3 | Both write their own rows based on outdated reads | RW<->RW cycle |
In real time, each transaction reads a current committed state. But in logical order, each depends on data that the other later changes, so the dependency graph forms a cycle, both relying on each other’s future writes.
Watching the Noose Tighten
So far, we’ve dissected conflicts in isolation, understanding how RW and WW edges shape every anomaly. But isolation levels aren’t just abstract rules about which edges are allowed or blocked. They are progressive states of restraint, each tightening its grip on these conflicts a little more.
Now that we know what needs to be contained, let’s see how different isolation levels actually do it, how each one pulls the rope tighter around RW and WW interactions until serializability finally holds.
1. Read Uncommitted: the Noose Is Off
The system neither blocks nor validates anything.
| Scenario | What Happens | Anomaly |
|---|---|---|
| T2 updates a row; T1 reads it before commit | T1 sees uncommitted data | Dirty Read (Allowed) |
| T1 re-reads after T2 commits | Values change mid-transaction | Non-repeatable Read (Allowed) |
| T1 reruns a range query after T2 inserts | Predicate returns new rows | Phantom Read (Allowed) |
| T1 and T2 write same record uncommitted | State depends on rollbacks | Dirty Write (Prevented) |
| T1 writes after stale read, T2 writes later | Earlier update lost | Lost Update (Allowed) |
| Two transactions read then write different rows | Invariant breaks | Write Skew (Allowed) |
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.
| Scenario | What Happens | Anomaly |
|---|---|---|
| Reads uncommitted data | Blocked / old value returned | Dirty Read (Prevented) |
| Re-reads same row after another commit | Sees new value | Non-repeatable Read (Allowed) |
| Re-runs predicate after insert | Sees new rows | Phantom Read (Allowed) |
| Concurrent writes to same row | Last writer wins | Lost Update (Allowed) |
| Overwrites uncommitted data | Forbidden | Dirty Write (Prevented) |
| Separate rows read-before-write | Invariant can break | Write Skew (Allowed) |
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.
| Scenario | What Happens | Anomaly |
|---|---|---|
| Reads uncommitted data | Hidden by snapshot | Dirty Read (Prevented) |
| Re-reads row later | Always same value | Non-repeatable Read (Prevented) |
| Range query after insert | New rows may appear | Phantom Read (Allowed) |
| Same row updated by two writers | Validation aborts one | Lost Update (Prevented) |
| Overwrite of uncommitted write | Forbidden | Dirty Write (Prevented) |
| Two writers read each other’s rows first | Both commit inconsistent state | Write Skew (Allowed) |
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.
| Scenario | What Happens | Anomaly |
|---|---|---|
| Reads uncommitted data | Never happens | Dirty Read (Prevented) |
| Re-reads row after commit | Stable snapshot or abort | Non-repeatable Read (Prevented) |
| Range query after insert | Cycle detected -> abort | Phantom Read (Prevented) |
| Concurrent writes | Ordered or aborted | Lost Update (Prevented) |
| Overwrites uncommitted data | Forbidden | Dirty Write (Prevented) |
| Cross-row invariant check | Cycle detected -> abort | Write Skew (Prevented) |
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
In this blog, we saw how all isolation levels are variations on a single theme, managing RW and WW conflicts. Relax both, and anomalies multiply. Restrain them carefully, and transactions appear serial.
Each level on the isolation spectrum represents a different policy for how much concurrency to risk before safety is enforced. At one end, performance thrives on trust; at the other, correctness reigns through control.
Understanding these edges and the patterns they form turns isolation levels from a checklist of anomalies into a system of cause and effect.
It’s not about memorising which anomaly happens where, but recognising how loosening or tightening the grip on conflicts shapes everything that follows.
To sum it all up. RW conflicts drive read-anomalies. WW conflicts drive write-anomalies. Their interaction drives the truly subtle anomalies (write skew, lost update). The tighter you grip around these two conflict types, the closer you move toward serializability.
| Conflict Type | Primary Anomaly Family | Typical Prevention | Isolation Levels That Block It |
|---|---|---|---|
| RW | Dirty Read, Non-repeatable Read, Phantom Read, Write Skew | Lock readers or use consistent snapshots | Read Committed -> Serializable |
| WW | Dirty Write, Lost Update | Lock writers or abort overlapping writes | All levels ≥ Read Committed |
| RW + WW (cyclic) | Write Skew, Lost Update (composite) | Detect cycles / serial-order validation | Serializable |