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.
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(), thencommitTransaction()orabortTransaction(). - 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
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'swithTransactionhelper. - Reading inside a transaction through the normal
dbhandle 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.