MongoDB from Basics to Production · MongoDB in Production

Multi-Document Transactions

Update several documents atomically with sessions and transactions, handle aborts, and know when single-document atomicity is enough.

Create a free account to save this page, take notes and track your progress. Sign in

What you'll build

A money transfer between two accounts plus a ledger entry, done in a transaction: either all three writes happen or none do. A failed transfer (insufficient funds) aborts and leaves everything unchanged.

The idea

  • Writes to a single document are always atomic, so good modelling often avoids transactions.
  • For changes across documents or collections, use a session with startTransaction(), then commitTransaction() or abortTransaction().
  • Transactions need a replica set (or sharded cluster); keep them short, since they hold locks and have a time limit (60 seconds by default).
  • Drivers offer withTransaction, which retries transient errors automatically.

The code

A transfer in a transaction
db.accounts.insertMany([{ _id: "ana", balance: 100 }, { _id: "ben", balance: 20 }]);

function transfer(from, to, amount) {
  const session = db.getMongo().startSession();
  const accounts = session.getDatabase(db.getName()).accounts;
  const ledger = session.getDatabase(db.getName()).ledger;
  session.startTransaction({ writeConcern: { w: "majority" } });
  try {
    const res = accounts.updateOne({ _id: from, balance: { $gte: amount } }, { $inc: { balance: -amount } });
    if (res.modifiedCount !== 1) throw new Error(`insufficient funds in ${from}`);
    accounts.updateOne({ _id: to }, { $inc: { balance: amount } });
    ledger.insertOne({ from, to, amount, at: new Date("2026-01-15T10:00:00Z") });
    session.commitTransaction();
    print(`transferred ${amount} from ${from} to ${to}`);
  } catch (e) {
    session.abortTransaction();
    print("aborted:", e.message);
  } finally {
    session.endSession();
  }
}

transfer("ana", "ben", 30);
transfer("ben", "ana", 500);
db.accounts.find().sort({ _id: 1 }).forEach(a => print(a._id, a.balance));
print("ledger entries:", db.ledger.countDocuments());

Output

transferred 30 from ana to ben
aborted: insufficient funds in ben
ana 70
ben 50
ledger entries: 1

Run this on your computer — see “Get Started” for setup.

Common mistakes

  • Using transactions for every write out of relational habit; embed data that changes together instead.
  • Long transactions (calling external APIs inside them) that hit time limits and block other writers.
  • Not retrying on TransientTransactionError; use the driver's withTransaction helper.
  • Reading inside a transaction through the normal db handle instead of the session, so reads aren't part of it.

Key points

  • Single-document writes are atomic; model to use them.
  • Transactions group writes across documents and collections.
  • Keep transactions short and let drivers retry transient errors.

Further reading: Transactions · Production considerations

Practice questions

When do you need a multi-document transaction in MongoDB?

Answer

When several documents or collections must change together atomically; single-document updates are already atomic.

Why keep transactions short?

Answer

They hold locks and resources, conflict with concurrent writes, and are aborted after a time limit (60 seconds by default).

What does a driver's withTransaction helper add?

Answer

It runs the callback in a transaction and retries automatically on transient errors and unknown commit results.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?