Thursday, April 16, 2026
All posts
Lv.3 IntermediateMongoDB
28 min readLv.3 Intermediate
SeriesMongoDB ACID Mastery · Part 3/3View series hub

MongoDB ACID — Part 3: Isolation — Snapshot Isolation and concurrency

MongoDB ACID — Part 3: Isolation — Snapshot Isolation and concurrency

Isolation in MongoDB is built from MVCC and Read Concern. This Part 3 walks through dirty reads, non-repeatable reads, phantoms, and read skew without forcing SQL isolation labels onto MongoDB, and clarifies what multi-document transactions with snapshot read concern actually guarantee. It connects fail-on-conflict behavior, client retries, and lock-acquisition timeouts into one mental model, compares local, majority, and snapshot read concerns with replication caveats, spells out prerequisites for causal consistency, and discusses write skew mitigations with their operational trade-offs. Part 4 will continue with durability and Write Concern.

Series outline

Table of contents

  1. Introduction
  2. Four isolation anomalies
  3. SQL isolation levels and MongoDB — why the matrix misleads
  4. MVCC — how MongoDB handles concurrency
  5. Snapshot isolation — where guarantees apply
  6. Read Concern — tuning read consistency
  7. Write conflicts: fail-on-conflict vs lock acquisition timeout
  8. Causal consistency — read the prerequisites
  9. Practical sketch — read concern and transactions
  10. Limits — write skew and mitigations
  11. Closing

1. Introduction

Among the four ACID properties, Isolation is the easiest to underestimate — and the richest source of “it worked in staging” stories.

Part 2 framed Atomicity and Consistency and told you to read readConcern and writeConcern together. This post narrows the lens to Isolation—what concurrent transactions can and cannot observe—and to read concern as the knob for read consistency. Write concern as a durability / replication lever returns in Part 4.

You might ask, “As long as I read committed data, I’m fine, right?” In practice, isolation is about which committed snapshot you read, and whether all reads in a workflow see the same one. A compact summary of MongoDB’s model is:

MVCC-based snapshot isolation — on conflict, fail fast and retry instead of waiting forever.

This article unpacks that line and separates what holds inside a multi-document transaction from what holds for ordinary single operations.


2. Four isolation anomalies

When transactions overlap, textbooks cite four patterns:

2.1 Dirty read

Reading data another transaction has not yet committed. If that transaction rolls back, your read never corresponded to a durable fact.

2.2 Non-repeatable read

Inside one transaction, two reads with the same predicate return different results because another transaction committed in between.

2.3 Phantom read

The set of matching rows/documents changes between reads in the same transaction (inserts/deletes).

2.4 Read skew

You read related values at skewed logical times — for example account A and B balances — and the combination is impossible in any real committed state.

These definitions are the ladder for everything that follows about snapshot reads and Read Concern.


3. SQL isolation levels and MongoDB — why the matrix misleads

The SQL standard describes isolation levels tuned for locking implementations. MongoDB uses WiredTiger MVCC, so a claim like “MongoDB equals REPEATABLE READ” is often more confusing than helpful.

Practical advice:

  • Do not treat the SQL table as MongoDB’s final answer.
  • Read Read Concern, whether you are in a transaction, and deployment topology (standalone / replica set / sharded) together.

3.1 Do not equate local with READ UNCOMMITTED

Blog posts sometimes label local “close to READ UNCOMMITTED.” Readers then import the SQL chart and imagine dirty reads as the headline risk.

In MongoDB, local is better read as the freshest data visible on the member you are connected to, plus replication caveats: during failovers, data you observed may roll back. That is closer to node visibility + durability trade-offs than to a one-to-one SQL label.

3.2 When people say “snapshots eliminate phantoms”

Inside a multi-document transaction with readConcern: "snapshot" (the transaction default), reads share one consistent snapshot. The guarantee attaches to that transaction boundary.

If you read outside a transaction, or you mix concerns across calls, you can still observe different logical times. Saying “snapshots erase every phantom everywhere” without that context over-promises. Name transaction + session + read concern whenever you make a strong claim.


4. MVCC — how MongoDB handles concurrency

Snapshot isolation assumes Multi-Version Concurrency Control (MVCC).

4.1 Core idea

Instead of overwriting a single row in place, writers create new versions; readers choose the version visible to them. That reduces unnecessary blocking between readers and writers.

4.2 WiredTiger and snapshots

WiredTiger decides visibility using transaction ids. A transaction’s snapshot determines which committed writes are visible, which is how uncommitted writes from other transactions stay invisible to you.


5. Snapshot isolation — where guarantees apply

The heart of snapshot isolation:

Reads inside one transaction behave against a single, stable snapshot.

While your transaction is open, other transactions may commit — yet your reads stay pinned to the snapshot you started with, which is why snapshot reads help with read skew inside that transaction.

5.1 Relation to anomalies (high level)

With snapshot read concern in a typical multi-document transaction, you usually reason about strong protection against dirty reads, non-repeatable reads inside the transaction, and read skew — inside that transaction. Write skew is different; see §10.


6. Read Concern — tuning read consistency

6.1 local

  • Returns the most recent data visible on the member.
  • Fast, but combined with replication, you must remember rollback windows after elections.
  • Mission-critical financial designs rarely stop at “default local only” without a conscious review.

6.2 majority

  • Reads data acknowledged by a majority of replica-set members — a more conservative bar than local for “will not roll back” reads in the usual replication story.

6.3 snapshot (multi-document transactions)

  • Provides a consistent snapshot across the transaction — the anchor for coordinated reads.

6.4 linearizable

  • Very strong, higher latency, narrow use cases (often single-document reads in documentation — verify your version).

6.5 Transaction-level options, not per-operation knobs

In multi-document transactions, you generally set Read Concern on startTransaction / withTransaction, not by hoping a per-find option overrides the transaction. Confirm details for your driver version.

await session.withTransaction(
  async () => {
    const doc = await collection.findOne({ _id: 1 }, { session });
    // ...
  },
  {
    readConcern: { level: "snapshot" },
    writeConcern: { w: "majority" },
  }
);

7. Write conflicts: fail-on-conflict vs lock acquisition timeout

Lock-heavy databases often block writers until the winner finishes. MongoDB’s document-level path is closer to optimistic conflict detection: conflicting writers fail quickly so the application can retry — the usual “fail-on-conflict” story.

7.1 Why avoid unbounded waiting

Unbounded waiting invites deadlock management costs. MongoDB prefers to surface conflicts early and push bounded retries to the client.

7.2 withTransaction and retries

session.withTransaction() can retry on transient errors such as those labeled TransientTransactionError. Production code should still cap retries, backoff, and preserve idempotency.

7.3 Not the same story as maxTransactionLockRequestTimeoutMillis

Mixing these causes fake contradictions:

  • Write conflict: another transaction already touched the data you need; you typically retry the transaction.
  • Lock acquisition timeout: you waited briefly to acquire a lock and timed out — see maxTransactionLockRequestTimeoutMillis in the manual (defaults vary by version; read the parameter docs).

So “Mongo doesn’t wait” and “there is a short lock wait” describe different layers.


8. Causal consistency — read the prerequisites

Causal consistency is about reading your own writes and related causal order in a session — but it is not “turn on a flag and every secondary magically catches up.”

Checklist:

  • Reuse the same client session where required.
  • Align read and write concern with what causal guarantees need (often majority writes matter).
  • If read preference targets secondaries, accept replication lag or design around it.

If reads use local while writes use majority without a coherent plan, your “just wrote then read” story can still break. Follow the causal consistency chapter for your server and driver versions.

const session = client.startSession({ causalConsistency: true });

await collection.updateOne(
  { _id: userId },
  { $set: { city: "Seoul" } },
  { session, writeConcern: { w: "majority" } }
);

const user = await collection.findOne(
  { _id: userId },
  { session, readConcern: { level: "majority" } }
);

9. Practical sketch — read concern and transactions

Imagine aggregating balances while a transfer transaction is mid-flight. If you must not see a half-applied transfer, a single local aggregation may be the wrong tool.

const session = client.startSession();
let total;

await session.withTransaction(
  async () => {
    const agg = await accounts
      .aggregate(
        [{ $group: { _id: null, total: { $sum: "$balance" } } }],
        { session }
      )
      .toArray();
    total = agg[0]?.total;
  },
  { readConcern: { level: "snapshot" }, writeConcern: { w: "majority" } }
);

You are asking for a transactional snapshot of the aggregate. Splitting reads with local across calls can stitch together different time slices.


10. Limits — write skew and mitigations

10.1 Write skew

Two transactions each read a snapshot, update different documents, and together violate a global rule (classic “at least one doctor on call”). Snapshot isolation does not automatically prevent that class of bug.

10.2 Dummy updates and trade-offs

A common pattern forces conflict by touching a shared document (sometimes via a _lastChecked field). That can work but adds audit noise, write amplification, and index pressure.

Alternatives:

  • Encode invariants in one atomic update where possible.
  • Use unique indexes / partial indexes to shrink the state space.
  • Remodel to reduce contention hot spots.

10.3 Do not mix read concerns inside one transaction

Design at transaction scope; avoid imagining per-operation overrides.


11. Closing

ThemeTakeaway
MVCCVersioned data reduces reader/writer blocking.
Snapshot isolationConsistent reads inside a transaction’s snapshot.
fail-on-conflictSurface conflicts early; pair with retries.
Read ConcernChanges what “read” means — local / majority / snapshot.
Write skewMay need modeling and constraints beyond snapshots alone.
Causal consistencyNeeds aligned session, concerns, and read preferences.

Part 4 continues with durability, WiredTiger journaling/checkpoints, and Write Concern under failure.


References


Written April 2026. Defaults, driver behavior, and parameter meanings change across versions — cite the manual for the build you run.

Share This Article

Series Navigation

MongoDB ACID Mastery

3 / 3 · 3

Explore this topic·Start with featured series

한국어

Follow new posts via RSS

Until the newsletter opens, RSS is the fastest way to get updates.

Open RSS Guide