Transactions in MongoDB
Starting with version 4.0, MongoDB supports multi-document ACID transactions. This means you can read and write to multiple documents across multiple collections atomically -- either all operations succeed or none of them are applied. Transactions are essential for operations like transferring funds between accounts, where partial updates would leave data in an inconsistent state.
Basic Transaction Flow
Transactions require a session. You start the session, begin a transaction, perform operations, then commit or abort:
const session = db.getMongo().startSession()
session.startTransaction()
try {
const accounts = session.getDatabase("bank").accounts
// Debit from source account
accounts.updateOne(
{ _id: "account-A" },
{ $inc: { balance: -100 } },
{ session }
)
// Credit to destination account
accounts.updateOne(
{ _id: "account-B" },
{ $inc: { balance: 100 } },
{ session }
)
// All operations succeeded -- commit
session.commitTransaction()
} catch (error) {
// Something went wrong -- abort all changes
session.abortTransaction()
throw error
} finally {
session.endSession()
}Cross-Collection Transactions
Transactions can span multiple collections, which is useful for maintaining referential integrity:
const session = db.getMongo().startSession()
session.startTransaction()
try {
const db = session.getDatabase("ecommerce")
// Create the order
db.orders.insertOne({
customerId: "cust-123",
items: [{ productId: "prod-A", qty: 2, price: 29.99 }],
total: 59.98,
status: "confirmed",
createdAt: new Date()
}, { session })
// Decrement inventory
db.products.updateOne(
{ _id: "prod-A", stock: { $gte: 2 } },
{ $inc: { stock: -2 } },
{ session }
)
// Record payment
db.payments.insertOne({
customerId: "cust-123",
amount: 59.98,
method: "credit_card",
processedAt: new Date()
}, { session })
session.commitTransaction()
} catch (error) {
session.abortTransaction()
throw error
} finally {
session.endSession()
}Transaction Options
You can configure read concern, write concern, and read preference for transactions:
session.startTransaction({
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" },
readPreference: "primary",
maxCommitTimeMS: 5000
})The "snapshot" read concern ensures consistent reads within the transaction. The "majority" write concern ensures durability.
Retry Logic
Transient transaction errors (such as write conflicts) can be retried. MongoDB drivers provide built-in retry helpers:
async function runTransactionWithRetry(session, txnFunc) {
while (true) {
try {
await txnFunc(session)
break
} catch (error) {
if (error.hasErrorLabel("TransientTransactionError")) {
console.log("Transient error, retrying transaction...")
continue
}
throw error
}
}
}
async function commitWithRetry(session) {
while (true) {
try {
await session.commitTransaction()
break
} catch (error) {
if (error.hasErrorLabel("UnknownTransactionCommitResult")) {
console.log("Retrying commit...")
continue
}
throw error
}
}
}When to Use Transactions
Transactions add overhead, so use them only when necessary:
- Use transactions for: Financial operations, inventory management, multi-collection updates requiring atomicity.
- Avoid transactions when: A single document update suffices, or when you can design your schema to keep related data in one document.
Well-designed schemas that embed related data often eliminate the need for multi-document transactions altogether.
Key Takeaways
- Transactions provide ACID guarantees across multiple documents and collections.
- Always use a session and wrap operations in try/catch with abort on failure.
- Implement retry logic for transient errors and unknown commit results.
- Prefer schema design that minimizes the need for transactions.
Try this query in UnifySQL
Write, optimize, and collaborate on MongoDB queries with AI assistance.
Start Free