Skip to main content
Articles/Databases/lost-update-database-anomaly
Scenario Article

Lost Update Explained: When Two Writes Become One

The Offline Spreadsheet Problem: What a Lost Update Actually Is

Abstract Algorithmsβ€’Apr 11, 2026
XLinkedInReddit
πŸ’‘ Executive Summary

A lost update occurs when two concurrent read-modify-write transactions both read the same committed value, both compute a new value from it, and both write back β€” with the second write silently discarding the first.

🎯 System Scenario & Requirements

System Goal

A lost update occurs when two concurrent read-modify-write transactions both read the same committed value, both compute a new value from it, and both write back β€” with the second write silently discarding the first.

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 lost update occurs when two concurrent read-modify-write transactions both read the same committed value, both compute a new value from it, and both write back β€” with the second write silently discarding the fir…
  • Both transactions report success.
  • The corruption is visible only in the final state.

Constraints & Assumptions

  • The two most common failure patterns in production: (a) under-protection β€” using Read Committed with a read-modify-write cycle and no locking, assuming "the database handles it" β€” it does not; and (b) over-protection —…

⚑ Execution Stages & Decision Trace

1

Stage 1 β€” The Flash Sale That Sold an Item It No Longer Had

Action / Decision:

At 09:00:00 UTC on a high-traffic Friday, FlashMart's engineering team triggered a flash sale on the last remaining unit of the Gaming Headset Pro (product id = 7842).

Component Behavior:

The item had been promoted to 400,000 newsletter subscribers for 48 hours.

Observable Output: By 09:00:01, two purchase requests arrived simultaneously from different data centers.
Read Full Stage in Deep Dive ↓
πŸ“–

Deep Dive

Detailed Architectural Analysis

Scenario context

A lost update occurs when two concurrent read-modify-write transactions both read the same committed value, both compute a new value from it, and both write back β€” with the second write silently discarding the first. No error is raised. Both transactions report success. The corruption is visible only in the final state. Atomic SQL operations (UPDATE SET val = val + 1) eliminate the vulnerability at any isolation l…

This walkthrough focuses on The Offline Spreadsheet Problem: What a Lost Update Actually Is.

Before making a design choice, clarify:

  • Before defining the anomaly precisely, consider an analogy that makes the mechanism obvious.
  • Two colleagues β€” Alice and Bob β€” each receive a printed copy of the same spreadsheet at 9 AM.
  • The spreadsheet shows the marketing budget remaining is $5,000.

Concepts in play: database-isolation-levels-anomalies, caching, consistency-replication, data-warehousing-lakehouse

Stage 1 β€” The Flash Sale That Sold an Item It No Longer Had

  • At 09:00:00 UTC on a high-traffic Friday, FlashMart's engineering team triggered a flash sale on the last remaining unit of the Gaming Headset Pro (product id = 7842).
  • The item had been promoted to 400,000 newsletter subscribers for 48 hours.
  • By 09:00:01, two purchase requests arrived simultaneously from different data centers.
  • Server A (us-east-1) received Customer Alice's request at 09:00:01.003.

Observable outcome: Server A (us-east-1) received Customer Alice's request at 09:00:01.003.

Stage 2 β€” The Read-Modify-Write Gap: Where the Anomaly Is Born

  • Lost updates emerge from a universal application pattern: the read-modify-write cycle .
  • A transaction reads the current state of a row, computes a new value in application memory, and writes the result back.
  • This three-step sequence contains a structural vulnerability: the gap between the read and the write.
  • During that gap, another transaction can perform its own read of the same row.
sequenceDiagram
    participant T1 as Transaction T1
    participant DB as Database
    participant T2 as Transaction T2
    T1->>DB: READ row value = V
    T2->>DB: READ row value = V
    Note over T1,T2: Both hold V in app memory - gap window open
    T1->>T1: Compute new value V1 from V
    T2->>T2: Compute new value V2 from V
    T1->>DB: WRITE value = V1
    DB-->>T1: Commit OK
    T2->>DB: WRITE value = V2
    DB-->>T2: Commit OK
    Note over DB: V2 overwrites V1 - T1 update is lost
Non-atomic cycle β€” vulnerable to lost update:

  1. T1: READ  inventory_count = 1   β†’ computes: 1 - 1 = 0
  2. T2: READ  inventory_count = 1   β†’ computes: 1 - 1 = 0
  3. T1: WRITE inventory_count = 0   β†’ committed
  4. T2: WRITE inventory_count = 0   β†’ overwrites T1, committed
  Final: inventory_count = 0  (two sales processed, one decrement lost)

Observable outcome: During that gap, another transaction can perform its own read of the same row.

Stage 3 β€” Lost Update in Slow Motion: A Transaction-Level Sequence

  • The following sequence diagram traces the exact database-level events during the FlashMart flash sale.
  • Pay attention to the overlap window between T1's SELECT and T2's SELECT β€” that is where the anomaly is born.
  • The diagram shows the four critical moments that produce the lost update: (1) both T1 and T2 read inventory count = 1 before either has written β€” they share an identical snapshot; (2) each independently computes the sam…
  • The database ends at inventory count = 0 , which is correct for one sale but wrong for two.
sequenceDiagram
    participant T1 as Transaction T1 - Server A
    participant DB as Stock Database
    participant T2 as Transaction T2 - Server B

    T1->>DB: BEGIN
    T2->>DB: BEGIN
    T1->>DB: SELECT inventory_count FROM products WHERE id=7842
    DB-->>T1: inventory_count = 1
    T2->>DB: SELECT inventory_count FROM products WHERE id=7842
    DB-->>T2: inventory_count = 1
    Note over T1: Computes 1 - 1 = 0, ready to write
    Note over T2: Computes 1 - 1 = 0, ready to write
    T1->>DB: UPDATE products SET inventory_count = 0 WHERE id=7842
    DB-->>T1: 1 row updated
    T1->>DB: COMMIT
    DB-->>T1: T1 committed successfully
    T2->>DB: UPDATE products SET inventory_count = 0 WHERE id=7842
    DB-->>T2: 1 row updated
    T2->>DB: COMMIT
    DB-->>T2: T2 committed successfully
    Note over DB: Final state: inventory_count = 0. T1 update overwritten.

Observable outcome: The database ends at inventory count = 0 , which is correct for one sale but wrong for two.

Stage 4 β€” Lost Update, Dirty Write, and Write Skew: Knowing the Difference Under Pressure

  • Lost updates are frequently confused with two related anomalies β€” dirty writes and write skew β€” in interviews and code reviews.
  • All three are write conflicts involving concurrent transactions, but they differ in the data each transaction reads and the rows each transaction writes.
  • Lost update is the only one of the three that involves exclusively committed reads.
  • T1 and T2 both see valid, committed data β€” the anomaly arises purely from the temporal gap between their respective reads and writes.

Observable outcome: T1 and T2 both see valid, committed data β€” the anomaly arises purely from the temporal gap between their respective reads and writes.

Stage 5 β€” How Databases Actually Detect (and Miss) Lost Updates

  • The most consequential practical distinction in production systems is not which isolation level is configured in theory β€” it is whether your specific database implementation detects lost updates at that level.
  • Internals: How MVCC and Write Tracking Create (and Close) the Lost-Update Window Every major database that supports snapshot-based isolation uses Multi-Version Concurrency Control (MVCC) .
  • When T1 reads a row, it does not acquire a traditional read lock β€” it receives a pointer to the most recently committed version of that row as of the transaction's start time.
  • The row itself can continue to receive new committed versions from other transactions.
ERROR:  could not serialize access due to concurrent update
DETAIL:  Process 12345 updated this row while this transaction was in progress.

Observable outcome: The row itself can continue to receive new committed versions from other transactions.

Stage 6 β€” Isolation Level Prevention Matrix: Which Levels Actually Catch Lost Updates

  • Not all isolation levels prevent lost updates, and β€” as MySQL's behavior demonstrates β€” even the same named level can behave differently across databases.
  • This table reflects both ANSI SQL standard semantics and the documented behavior of the three most common production databases.
  • The practical takeaway from this table: never assume your isolation level prevents lost updates without verifying your database's specific implementation.
  • In MySQL, treat Repeatable Read as equivalent to Read Committed for lost-update purposes.

Observable outcome: In MySQL, treat Repeatable Read as equivalent to Read Committed for lost-update purposes.

End-to-end outcome

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

  • Silence is the most dangerous symptom.
  • Lost updates produce no errors, no warnings, and no log entries at the database or application layer.
    • A lost update occurs when two read-modify-write transactions overwrite each other β€” one write silently discards the other, with no error raised by either the database or the application.
    • The root cause is the read-modify-write gap : T2 reads the same committed value T1 already read, and T2's write lands after T1's β€” overwriting T1's result using a stale starting point.

Alternatives considered

Choosing the Right Prevention Strategy: A Decision Guide

The following flowchart captures the practical decision logic for selecting a lost-update prevention strategy.

Failure and degraded modes

Trade-offs: What Each Prevention Strategy Costs in Throughput and Complexity

  • Detection: The two most common failure patterns in production: (a) under-protection β€” using Read Committed with a read-modify-write cycle and no locking, assuming "the database handles it" β€” it does not; and (b…
  • User or operator impact: The two most common failure patterns in production: (a) under-protection β€” using Read Committed with a read-modify-write cycle and no locking, assuming "the database handles it" β€” it does not; and (b…

Share this case study

Discuss architecture trade-offs and lessons with your team.

Share:XLinkedInReddit