Skip to main content
Articles/Databases/dirty-write-database-anomaly
Scenario Article

Dirty Write Explained: When Uncommitted Data Gets Overwritten

Deep Dive: Why Cross-Table Dirty Writes Create Permanently Inconsistent State

Abstract Algorithms•Apr 11, 2026
💡 Executive Summary

A dirty write occurs when Transaction B overwrites data that Transaction A has written but not yet committed.

🎯 System Scenario & Requirements

System Goal

A dirty write occurs when Transaction B overwrites data that Transaction A has written but not yet committed.

Actors & Components

  • Application Workers
  • Volatile Memory Tier
  • Persistent Disk Tier
  • Worker Process
  • In-Memory Cache
  • Primary Database
  • Edge Traffic Ingress
  • Stateless Microservices

Functional Requirements

  • TLDR: A dirty write occurs when Transaction B overwrites data that Transaction A has written but not yet committed.
  • The result is not a rollback or an error — it is silently inconsistent committed data: one table reflects Transaction B's intent, another still reflects Transaction A's.
  • Every mainstream relational database prevents dirty writes automatically via row-level write locks held until commit.

Constraints & Assumptions

  • It is 14:32:01 on a Saturday afternoon.
  • A live car auction is in its final seconds.

⚡ Execution Stages & Decision Trace

1

Stage 1 — What Makes a Write "Dirty": Uncommitted Data in the Crossfire

Action / Decision:

The word "dirty" in database terminology always means uncommitted .

Component Behavior:

Data that has been written by a transaction but not yet committed exists in a state of uncertainty — it may be rolled back, it may be committed, but it has no permanent reality yet.

Observable Output: A dirty read occurs when Transaction B reads that uncertain, uncommitted data.
Read Full Stage in Deep Dive ↓
📖

Deep Dive

Detailed Architectural Analysis

Scenario context

A dirty write occurs when Transaction B overwrites data that Transaction A has written but not yet committed. The result is not a rollback or an error — it is silently inconsistent committed data: one table reflects Transaction B's intent, another still reflects Transaction A's. Every mainstream relational database prevents dirty writes automatically via row-level write locks held until commit. The danger re-emerges…

This walkthrough focuses on Deep Dive: Why Cross-Table Dirty Writes Create Permanently Inconsistent State.

Before making a design choice, clarify:

  • The auction example involves two tables: cars and invoices .
  • This is not incidental — it is what makes dirty writes particularly devastating in practice.
  • When a transaction spans multiple tables, its business logic depends on all writes succeeding or failing together.

Concepts in play: database-isolation-levels-anomalies, caching, consistency-replication, jvm-internals-and-garbage-collection

Stage 1 — What Makes a Write "Dirty": Uncommitted Data in the Crossfire

  • The word "dirty" in database terminology always means uncommitted .
  • Data that has been written by a transaction but not yet committed exists in a state of uncertainty — it may be rolled back, it may be committed, but it has no permanent reality yet.
  • A dirty read occurs when Transaction B reads that uncertain, uncommitted data.
  • It is looking at something that may never have existed.

Observable outcome: It is looking at something that may never have existed.

Stage 2 — How Dirty Writes Corrupt Two Tables at Once: The Step-by-Step Mechanics

  • The sequence below shows exactly how the auction failure unfolds at the database level, without row-level locking protection.
  • Each arrow is a database operation; the Notes show the intermediate state of the cars row.
  • The key moment is the second UPDATE by T2 Bob: it hits the cars row while T1 Alice's write is still open, overwriting it before T1 has any chance to commit or roll back.
  • From that point on, T1 is writing to the invoices table based on the assumption that it owns the car — but the car row no longer reflects T1's intent.
sequenceDiagram
    participant T1 as T1 Alice
    participant DB as Database
    participant T2 as T2 Bob

    T1->>DB: BEGIN
    T1->>DB: UPDATE cars SET owner=alice WHERE id=CAR-4521
    Note over DB: cars row: owner=alice, uncommitted. T1 still open.
    T2->>DB: BEGIN
    T2->>DB: UPDATE cars SET owner=bob WHERE id=CAR-4521
    Note over DB: Dirty write. Bob overwrites Alice uncommitted row.
    T1->>DB: UPDATE invoices SET buyer=alice, amount=28500
    T1->>DB: COMMIT
    Note over DB: cars.owner=bob, invoices.buyer=alice. Both committed.
    T2->>DB: COMMIT
    Note over DB: Final state is permanently inconsistent across tables.

Observable outcome: From that point on, T1 is writing to the invoices table based on the assumption that it owns the car — but the car row no longer reflects T1's intent.

Stage 3 — Concurrent Buyer Requests, One Database Row: The System Architecture View

  • The following diagram shows how the auction service's two concurrent request threads interact with the cars and invoices databases.
  • The two UPDATE arrows converging on the Car DB node represent the dirty write — both threads are writing to the same row without serialization, producing the inconsistent committed state at the bottom.
  • This architecture-level view makes clear why application-layer validation is insufficient: both threads passed the business validation step (both confirmed the car was available), and both proceeded to write.
  • The race condition lives entirely within the database write path.
graph TD
    A[Buyer A Browser] -->|POST /auction/bid at T=103ms| B[Auction Service]
    C[Buyer B Browser] -->|POST /auction/bid at T=187ms| B
    B -->|T1: UPDATE cars SET owner=alice| D[Car DB - CAR-4521 row]
    B -->|T2: overwrite owner=bob before T1 commits| D
    B -->|T1: UPDATE invoices SET buyer=alice| E[Invoice DB - INV-9901 row]
    D -->|After both commits| F[cars.owner = bob]
    E -->|After T1 commit| G[invoices.buyer = alice]
    F --> H[Permanently inconsistent state across tables]
    G --> H

Observable outcome: The race condition lives entirely within the database write path.

Stage 4 — Which Isolation Levels Prevent Dirty Writes — and Which Anomalies They Still Allow

  • Dirty writes sit at the absolute bottom of the anomaly severity scale: they are prevented by every practical isolation level.
  • The table below shows the full picture across all standard levels.
  • In practice, even Read Uncommitted in PostgreSQL, MySQL, and SQL Server acquires write locks, making dirty writes impossible regardless of stated isolation level.
  • † MySQL InnoDB Repeatable Read prevents phantom reads via gap locks.

Observable outcome: † MySQL InnoDB Repeatable Read prevents phantom reads via gap locks.

Stage 5 — When Dirty Writes Re-Emerge in the Real World: Distributed System Edge Cases

  • Within a single relational database, dirty writes are a solved problem.
  • The challenge reappears when data is distributed across nodes, services, or stores that lack a unified lock manager.
  • In a multi-master setup (CouchDB, MySQL Group Replication in multi-primary mode, Cassandra with multiple coordinators), two nodes can accept conflicting writes to the same logical record concurrently.
  • Each node acquires its local row lock, preventing dirty writes on that node , but there is no global lock across nodes.

Observable outcome: Each node acquires its local row lock, preventing dirty writes on that node , but there is no global lock across nodes.

Stage 6 — All Seven Concurrency Anomalies: Where Dirty Write Fits in the Map

  • Understanding dirty writes fully requires placing them in the context of the complete anomaly taxonomy.
  • The table below maps every known concurrency anomaly across the four dimensions that determine where it sits in the prevention hierarchy.
  • Dirty write and dirty read are the only two anomalies that involve uncommitted data.
  • They are prevented earliest in the isolation hierarchy (Read Committed).

Observable outcome: They are prevented earliest in the isolation hierarchy (Read Committed).

End-to-end outcome

After working through the stages, you should be able to explain:

  • Default isolation levels in relational databases silently prevent dirty writes — but that protection ends at the database boundary.
  • Every SQL database at Read Committed or above blocks dirty writes.
  • A dirty write is the result of Transaction B overwriting Transaction A's uncommitted data before T1 finishes.
  • Because both transactions ultimately commit, the resulting inconsistency is permanent: different tables reflect the intents of different transactions, with no error, no rollback, and no automatic detection.

Alternatives considered

Choosing Write Isolation: Consistency Guarantees vs. Concurrent Throughput

Preventing dirty writes is not free.

Failure and degraded modes

One Car, Two Owners: The Auction Failure That Should Never Happen

  • Detection: It is 14:32:01 on a Saturday afternoon.
  • User or operator impact: It is 14:32:01 on a Saturday afternoon.

Share this case study

Discuss architecture trade-offs and lessons with your team.

Share:XLinkedInReddit