Use a MongoDB transaction only when one atomic rule spans multiple documents; keep sessions short and make retry behavior explicit.
Since MongoDB 4.0, multi-document ACID transactions are supported on replica sets, and since 4.2 on sharded clusters. Transactions guarantee Atomicity (all or nothing), Consistency (data remains valid), Isolation (concurrent transactions don't interfere), and Durability (committed data persists).
For single-document operations, MongoDB has always been atomic. Multi-document transactions are needed when you need to update multiple documents or collections atomically.
// Example: Transfer funds between two accounts atomically
const session = db.getMongo().startSession()
session.startTransaction({
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" }
})
try {
const accounts = session.getDatabase("bank").accounts
// Debit from sender
accounts.updateOne(
{ accountId: "ACC001", balance: { $gte: 500 } },
{ $inc: { balance: -500 } },
{ session }
)
// Credit to receiver
accounts.updateOne(
{ accountId: "ACC002" },
{ $inc: { balance: 500 } },
{ session }
)
// Commit both operations atomically
session.commitTransaction()
print("Transaction committed successfully")
} catch (error) {
// Rollback all changes if anything fails
session.abortTransaction()
print("Transaction aborted: " + error.message)
} finally {
session.endSession()
}
// withTransaction() handles retries and session cleanup automatically
const session = db.getMongo().startSession()
session.withTransaction(async () => {
const orders = session.getDatabase("myapp").orders
const inventory = session.getDatabase("myapp").inventory
// Create the order
await orders.insertOne({
userId: ObjectId("u1"),
productId: ObjectId("prod1"),
qty: 2,
total: 259.98,
status: "confirmed",
createdAt: new Date()
}, { session })
// Decrement inventory
await inventory.updateOne(
{ productId: ObjectId("prod1"), stock: { $gte: 2 } },
{ $inc: { stock: -2 } },
{ session }
)
})
session.endSession()
// Transaction options
session.startTransaction({
readConcern: { level: "snapshot" }, // snapshot, majority, local
writeConcern: { w: "majority", j: true, wtimeout: 5000 },
maxCommitTimeMS: 10000 // max time to wait for commit
})
// Read concern levels:
// "local" - reads most recent data (may not be committed)
// "majority" - reads data confirmed by majority of replica set
// "snapshot" - reads consistent snapshot at transaction start (recommended)
// Write concern:
// w: 1 - acknowledged by primary only
// w: "majority" - acknowledged by majority of replica set (recommended)
// j: true - write to journal before acknowledging
// Key limitations:
// - Transactions have a 60-second default timeout
// - Maximum 1000 documents modified per transaction (configurable)
// - Cannot create or drop collections inside a transaction
// - Avoid long-running transactions - they hold locks
// - Prefer single-document operations when possible (always atomic)
// - Requires a replica set (even a single-node replica set works)
MongoDB transactions allow multiple operations to commit or abort as one unit. They are useful when separate documents must stay consistent, such as transferring money between accounts or updating inventory and order records together. If one operation fails, the transaction can abort so partial changes do not remain.
Transactions are powerful but should not be the first design tool for every write. MongoDB document modeling often avoids transactions by keeping data that changes together inside one document. Use transactions when consistency across multiple documents or collections is genuinely required, and keep them short to reduce locks and retry complexity.
const session = client.startSession();
await session.withTransaction(async () => {
await accounts.updateOne({ _id: from }, { '$inc': { balance: -100 } }, { session });
await accounts.updateOne({ _id: to }, { '$inc': { balance: 100 } }, { session });
});
Use one when related changes across documents must commit or roll back together.
No. A write to one document is already atomic.
Transient conflicts or replica-set changes can abort an attempt, so the operation should tolerate retry.
Explore 500+ free tutorials across 20+ languages and frameworks.