MongoDB Transactions
ACID Transactions in MongoDB
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.
Using Transactions with Sessions
// 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() Helper
// 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 and Best Practices
// 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)