MongoDB Transactions
Transaction Basics (mongosh)
Multi-document transactions require a replica set or sharded cluster. Available since MongoDB 4.0.
// Start a session and transaction
const session = db.getMongo().startSession();
session.startTransaction({
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" }
});
try {
const orders = session.getDatabase("shop").orders;
const inventory = session.getDatabase("shop").inventory;
orders.insertOne(
{ userId: "u1", productId: "p1", qty: 2, total: 49.98 },
{ session }
);
inventory.updateOne(
{ productId: "p1" },
{ $inc: { stock: -2 } },
{ session }
);
session.commitTransaction();
print("Transaction committed");
} catch (err) {
session.abortTransaction();
print("Transaction aborted:", err);
} finally {
session.endSession();
}
Node.js Driver Example
// Using withTransaction() helper (auto-retry on transient errors)
const { MongoClient } = require("mongodb");
const client = new MongoClient(uri);
async function transferFunds(fromId, toId, amount) {
const session = client.startSession();
try {
await session.withTransaction(async () => {
const accounts = client.db("bank").collection("accounts");
const from = await accounts.findOne({ _id: fromId }, { session });
if (from.balance < amount) throw new Error("Insufficient funds");
await accounts.updateOne(
{ _id: fromId },
{ $inc: { balance: -amount } },
{ session }
);
await accounts.updateOne(
{ _id: toId },
{ $inc: { balance: amount } },
{ session }
);
}, {
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" },
readPreference: "primary"
});
} finally {
await session.endSession();
}
}
Retryable Writes
Retryable writes automatically retry once on network errors and primary failovers, enabled by default in drivers 3.6+.
// Enable in connection string (default in modern drivers)
const client = new MongoClient("mongodb://host:27017/db?retryWrites=true");
// Retryable writes work for:
// - insertOne, insertMany
// - updateOne, replaceOne
// - deleteOne
// - findOneAndUpdate, findOneAndReplace, findOneAndDelete
// - bulkWrite (ordered and unordered)
// NOT retryable: multi-document writes (updateMany, deleteMany)
// NOT retryable: operations inside transactions (managed separately)
// Disable retryable writes (e.g., for legacy compatibility)
const client2 = new MongoClient(uri, { retryWrites: false });
Causal Consistency
Causal consistency ensures a session reads its own writes, even against secondary nodes.
// Causally consistent session
const session = client.startSession({ causalConsistency: true });
// Write to primary
await db.collection("settings").updateOne(
{ key: "theme" },
{ $set: { value: "dark" } },
{ session, writeConcern: { w: "majority" } }
);
// Read from secondary โ guaranteed to see the write above
await db.collection("settings").findOne(
{ key: "theme" },
{
session,
readPreference: "secondary",
readConcern: { level: "majority" }
}
);
Transaction Limits & Best Practices
| Constraint | Default / Recommendation |
|---|---|
| Max transaction duration | 60 seconds (transactionLifetimeLimitSeconds) |
| Max document lock count | No hard limit, but keep small |
| Oplog entry size limit | 16 MB per transaction |
| Create collections in txn | Supported since MongoDB 4.4 |
| Keep transactions short | Minimize lock contention and WiredTiger conflicts |
| Avoid long-running queries | Prefer pre-computed or indexed lookups inside txn |