A transaction is a group of database operations that are treated as one logical unit. Transactions help ensure that related changes either all succeed or are rolled back if the transaction fails.
Imagine that a student pays ₹2,000 in fees. Your application needs to insert a payment record and update the student's paid-fee total.
If the payment is recorded but the student's paid-fee total is not updated, the data may become inconsistent. A transaction can group these operations together.
Transactions follow the ACID properties:
Transactions are useful when several related operations must succeed together.
MongoDB provides atomicity for individual document writes even without an explicit multi-document transaction. Use a transaction when your operation requires atomicity across multiple documents.
Multi-document transactions require a supported MongoDB deployment.
For local practice, configure MongoDB as a single-node replica set or use an appropriate replica-set deployment. Simply running a standalone server is not sufficient.
Check your server version:
db.version()
Check the current deployment's replica-set status:
rs.status()
The second command works when connected to a replica-set member with the required permissions.
We will simulate a money transfer between two bank accounts. Each account has an account number and a balance.
use bankdb
db.accounts.insertMany([
{
account_no: 101,
holder: "Rahul",
balance: 5000
},
{
account_no: 102,
holder: "Priya",
balance: 3000
}
])
Check the account balances:
db.accounts.find()
Note: Run the sample insertion only once. If these account numbers already exist in your practice collection, use a fresh collection or remove the sample data before inserting it again.
In mongosh, use a session to start a transaction.
All database operations that belong to the transaction
must use the same session.
const session = db.getMongo().startSession();
const bank = session.getDatabase("bankdb");
session.startTransaction();
try {
bank.accounts.updateOne(
{ account_no: 101, balance: { $gte: 1000 } },
{ $inc: { balance: -1000 } }
);
bank.accounts.updateOne(
{ account_no: 102 },
{ $inc: { balance: 1000 } }
);
session.commitTransaction();
print("Transaction committed successfully.");
} catch (error) {
session.abortTransaction();
print("Transaction failed: " + error.message);
} finally {
session.endSession();
}
This example demonstrates the session API. In production code, verify that the sender update actually matched a document before crediting the recipient. Otherwise, the recipient could be credited even when the sender's balance was insufficient.
A safer, complete example is provided in the next section.
The following example checks the sender's balance and verifies that the sender update matched one account. If it did not, an error is thrown and the transaction is aborted.
const session = db.getMongo().startSession();
try {
const bank = session.getDatabase("bankdb");
session.startTransaction();
const debitResult = bank.accounts.updateOne(
{
account_no: 101,
balance: { $gte: 1000 }
},
{
$inc: { balance: -1000 }
}
);
if (debitResult.matchedCount !== 1) {
throw new Error(
"Sender account not found or insufficient balance."
);
}
const creditResult = bank.accounts.updateOne(
{
account_no: 102
},
{
$inc: { balance: 1000 }
}
);
if (creditResult.matchedCount !== 1) {
throw new Error("Recipient account not found.");
}
session.commitTransaction();
print("Money transferred successfully.");
} catch (error) {
try {
session.abortTransaction();
} catch (abortError) {
print("Could not abort transaction: " + abortError.message);
}
print("Transaction failed: " + error.message);
} finally {
session.endSession();
}
Expected balances after a successful transfer:
[
{
account_no: 101,
holder: "Rahul",
balance: 4000
},
{
account_no: 102,
holder: "Priya",
balance: 4000
}
]
The balances change from ₹5,000 and ₹3,000 to ₹4,000 and ₹4,000. The total remains ₹8,000.
This example assumes the sample accounts exist and the transaction is run only once. Repeating the transaction transfers another ₹1,000, provided the sender still has sufficient funds.
The commitTransaction() method commits
the transaction. If it succeeds, the changes become
part of the database.
session.commitTransaction();
Use it after all the required operations have completed successfully.
Do not commit the transaction before verifying the results of operations that are essential to the task.
The abortTransaction() method aborts an
active transaction and discards its uncommitted changes.
session.abortTransaction();
For example, if an account transfer cannot credit the recipient, the transaction should be aborted so the debit is not committed by itself.
Aborting a transaction does not undo operations that were already committed outside that transaction.
A transaction can also group insert operations across multiple collections.
const session = db.getMongo().startSession();
try {
const shop = session.getDatabase("shopdb");
session.startTransaction();
shop.orders.insertOne(
{
order_id: 1001,
customer: "Rahul",
total: 2500
},
{ session: session }
);
shop.payments.insertOne(
{
order_id: 1001,
amount: 2500,
status: "Paid"
},
{ session: session }
);
session.commitTransaction();
print("Order and payment saved.");
} catch (error) {
try {
session.abortTransaction();
} catch (abortError) {
print("Abort failed: " + abortError.message);
}
print("Transaction failed: " + error.message);
} finally {
session.endSession();
}
Both collections must be available in a deployment that supports transactions. The collections should normally be created before starting the transaction, rather than relying on implicit collection creation.
The database and collections in this example are for
illustration. Create orders and
payments in shopdb before
running it.
| Single-Document Operation | Multi-Document Transaction |
|---|---|
| Atomic changes to one document | Atomic changes across multiple documents |
| Usually simpler and more efficient | Has additional resource and coordination overhead |
| Does not require a multi-document transaction | Requires a supported replica set or sharded cluster |
| Good when all required data is in one document | Useful when related changes span multiple documents |
Whenever possible, use a data model that lets you perform a task with a single atomic document operation. Use transactions when multiple-document atomicity is needed.
Production applications should follow the retry and transaction-handling recommendations for their MongoDB driver. A failed commit can sometimes require retrying the commit rather than immediately restarting the entire transaction.
Practice the following transaction tasks:
1. What does a transaction help guarantee across multiple documents?
2. Which method commits a transaction in mongosh?
3. Which deployment supports multi-document transactions?