MongoDB transactions group several database operations into one logical action. Either MongoDB saves every change or discards every change. This protects related data from partial updates.
Consider a money transfer between two accounts. MongoDB must subtract the amount from one account, add it to another, and record the transfer. Saving only some of these changes would produce incorrect data. A transaction makes the complete transfer atomic.
MongoDB can run transactions across multiple documents, collections, databases, and shards. Use them when several related operations must succeed or fail together.
Important: Every write to a single MongoDB document is already atomic. Design documents carefully and use multi-document transactions only when one document cannot safely contain the related data.
How MongoDB Transactions Work
A transaction follows the ACID properties. These rules help keep data dependable when an operation contains several steps.
- Atomicity: MongoDB saves all transaction changes or none of them.
- Consistency: The transaction moves the database from one valid state to another.
- Isolation: Other operations cannot see uncommitted transaction changes.
- Durability: After a successful commit, the selected write concern controls how strongly MongoDB confirms the saved changes.
A successful transaction ends with a commit. If an operation fails, the transaction ends with a rollback. Rolled-back changes never become visible outside the transaction.
When to Use a Transaction
Use a transaction when partial completion would make your data invalid. Account transfers, order and inventory updates, booking systems, and related audit records are common examples.
You usually do not need a transaction when one document can hold all related information. For example, updating a customer and an embedded address in the same document is already atomic.
Transactions add coordination and resource costs. They support good schema design; they do not replace it. Embedding related data can often provide a simpler and faster solution.
Deployment Prerequisites
Multi-document transactions work on replica sets and sharded clusters. They do not work on standalone MongoDB deployments.
Before testing the examples, connect mongosh to a replica set or sharded cluster. MongoDB Atlas clusters support transactions. For a self-managed local environment, configure a replica set instead of starting one standalone server.
A session can have only one open transaction at a time. The collections used by the examples should also exist before the transaction begins.
Prepare the Example Data
The following example uses two accounts belonging to Priya and Arjun. It also uses a separate collection to record completed transfers.
Example:
// Select the database used by this tutorial
db = db.getSiblingDB("communityFund");
// Create the collections before starting a transaction
db.createCollection("accounts");
db.createCollection("transfers");
// Add two familiar sample accounts
db.accounts.insertMany([
{ name: "Priya", balance: 8000 },
{ name: "Arjun", balance: 2500 }
]);
Priya currently has ₹8,000, while Arjun has ₹2,500. The transaction will transfer ₹1,500 from Priya to Arjun and create a matching transfer record.
Sessions, Commit, and Rollback
Every transaction belongs to a session. Start one from the current mongosh connection with db.getMongo().startSession().
After starting the session, use session.getDatabase() to obtain a database object linked to that session. Operations performed through this object participate in the session's active transaction.
A commit saves all changes and ends the transaction. An abort ends the transaction and discards its uncommitted changes. You should also end the session after finishing the work.
Run a Transaction with Session.withTransaction()
Session.withTransaction() provides the simplest way to manage a transaction in mongosh. It runs a callback inside the transaction and automatically commits when the callback finishes successfully.
If the callback throws an error, the method rolls back the uncommitted changes. It can also retry the commit after a commit failure or retry the whole transaction when the error permits.
Example:
// Start a session that reads from the primary
const session = db.getMongo().startSession({
readPreference: { mode: "primary" }
});
// Access the database through the session
const fundDB = session.getDatabase("communityFund");
// Set options for the complete transaction
const transactionOptions = {
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" },
readPreference: "primary"
};
try {
const transferId = session.withTransaction(async () => {
// Deduct ₹1,500 only when Priya has enough money
const debitResult = fundDB.accounts.updateOne(
{ name: "Priya", balance: { $gte: 1500 } },
{ $inc: { balance: -1500 } }
);
if (debitResult.modifiedCount !== 1) {
// Throwing an error causes the transaction to roll back
throw new Error("Priya has insufficient funds.");
}
// Add the same amount to Arjun's account
const creditResult = fundDB.accounts.updateOne(
{ name: "Arjun" },
{ $inc: { balance: 1500 } }
);
if (creditResult.modifiedCount !== 1) {
throw new Error("Arjun's account was not found.");
}
// Record the transfer in the same transaction
const logResult = fundDB.transfers.insertOne({
from: "Priya",
to: "Arjun",
amount: 1500,
status: "completed",
createdAt: new Date()
});
// Return a value after all operations succeed
return logResult.insertedId;
}, transactionOptions);
print("Transaction committed. Transfer ID:", transferId);
} catch (error) {
// No transaction changes remain after rollback
print("Transaction failed:", error.message);
} finally {
// Release the session after the transaction finishes
session.endSession();
}
Each collection operation uses fundDB, which came from the session. Do not switch back to the ordinary global database object inside the callback, because that operation would not belong to the transaction.
The balance condition also protects against an overdraft. If Priya has less than ₹1,500, the first update changes no document. The code then throws an error, so MongoDB does not update Arjun or insert the transfer record.
Transaction Options
Transaction-level options control the consistency of reads, the acknowledgment of the final result, and where MongoDB sends read operations.
| Option | Purpose |
|---|---|
| readConcern | Controls which version of the data the transaction reads. |
| writeConcern | Controls the acknowledgment MongoDB uses when committing or aborting the transaction. |
| readPreference | Controls which replica-set member handles reads. Transactions containing reads use the primary. |
The snapshot read concern gives the transaction a consistent view of the data. On a sharded cluster, it provides a synchronized snapshot across the involved shards.
The majority write concern waits until a calculated majority of voting data-bearing members acknowledges the commit. This offers stronger protection against rollback during a replica-set failover than acknowledgment from the primary alone.
Set read concern and write concern for the complete transaction. Do not place a separate write concern on individual write operations inside it.
Control a Transaction Manually
You can manage each stage yourself with Session.startTransaction(), Session.commitTransaction(), and Session.abortTransaction(). This approach gives you more control, but you must handle failures and retry decisions carefully.
Example:
// Create a separate session for manual transaction control
const manualSession = db.getMongo().startSession({
readPreference: { mode: "primary" }
});
const manualDB = manualSession.getDatabase("communityFund");
try {
// Begin a transaction with consistency options
manualSession.startTransaction({
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" }
});
// Make related changes through the session database
manualDB.accounts.updateOne(
{ name: "Arjun" },
{ $inc: { rewardPoints: 10 } }
);
manualDB.transfers.insertOne({
account: "Arjun",
type: "reward",
points: 10,
createdAt: new Date()
});
// Save both changes together
manualSession.commitTransaction();
print("Manual transaction committed.");
} catch (error) {
// Discard all uncommitted changes
manualSession.abortTransaction();
print("Manual transaction rolled back:", error.message);
} finally {
// Always close the session
manualSession.endSession();
}
startTransaction() associates the new transaction with the session. The server begins the transaction when the first session operation runs. A session cannot contain two active transactions at the same time.
Supported and Restricted Operations
Transactions support common CRUD operations such as finding, inserting, updating, replacing, and deleting documents. They also support many aggregation operations and can work across collections and databases.
Some operations have restrictions. You cannot write to capped collections, system collections, or collections in the admin, config, and local databases. Commands that inspect query plans and several administrative operations are unavailable inside transactions.
Some collection and index creation operations are possible under specific conditions, but production code is easier to manage when collections and indexes already exist.
Runtime and Write Conflicts
By default, MongoDB ends transactions that run longer than 60 seconds. Long transactions also consume storage-engine cache, hold resources, and increase the chance of conflicts.
A write conflict can occur when an outside operation changes a document that an active transaction later tries to change. MongoDB then aborts the transaction. Session.withTransaction() can repeat the transaction when the error allows it.
Because the callback may run again, keep it limited to MongoDB operations whose repeated execution remains safe. Avoid long calculations, user input, waiting, or unrelated work inside the callback.
Tip: Keep transactions short, focused, and predictable. The smaller the transaction, the less time it holds resources and the lower the chance of a conflict.
MongoDB Transaction Best Practices
- Use a transaction only when several changes must succeed or fail together.
- Prefer single-document atomic writes when your schema supports them.
- Access every collection through the same session database object.
- Create collections and indexes before regular transaction activity begins.
- Use suitable indexes so reads and updates finish quickly.
- Check update results and throw an error when a business rule fails.
- Keep retryable transaction work safe to run again.
- Test commits, rollbacks, insufficient data, and write conflicts.
Conclusion
MongoDB transactions protect related changes across multiple documents, collections, databases, or shards. A session groups the operations, a commit saves them together, and a rollback removes all uncommitted changes.
Use Session.withTransaction() for automatic commit, rollback, and permitted retries. Choose manual control only when you need to manage each transaction stage yourself. Most importantly, keep transactions short and use them alongside effective document design.