Lesson 30 of 50 – Transactions in MongoDB
60%

Transactions in MongoDB

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.

1. What Is a Transaction?

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:

  • Atomicity: All operations in the transaction succeed together, or the transaction is rolled back.
  • Consistency: The application moves the database from one valid state to another.
  • Isolation: Concurrent transactions are handled according to MongoDB's transaction isolation and concurrency rules.
  • Durability: Once committed successfully, changes are persisted according to the configured write concern.

2. When Should You Use Transactions?

Transactions are useful when several related operations must succeed together.

  • Transferring money between accounts.
  • Recording a payment and updating a balance.
  • Creating an order and reducing inventory.
  • Updating multiple related documents.
  • Maintaining consistency across multiple collections.

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.

3. Requirements for MongoDB Transactions

Multi-document transactions require a supported MongoDB deployment.

  • Transactions are supported on replica sets.
  • Transactions are supported on sharded clusters.
  • A standalone MongoDB server does not support multi-document transactions.
  • Use a supported MongoDB version and compatible driver.

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.

4. Create Sample Collections

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.

5. Start a Transaction in mongosh

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.

6. Complete Money Transfer Transaction

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.

7. What Is commitTransaction()?

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.

8. What Is abortTransaction()?

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.

9. Insert Multiple Documents in a 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.

10. Transaction vs Single-Document Atomicity

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.

11. Transaction Best Practices

  • Keep transactions short.
  • Use a supported replica set or sharded cluster.
  • Pass the same session to every operation in the transaction.
  • Verify important update and delete results.
  • Commit only after all required operations succeed.
  • Abort the transaction when an essential operation fails.
  • Handle transient transaction errors and retry according to MongoDB driver guidance.
  • Avoid unnecessary external operations while a transaction is open.
  • Consider write concern and read concern requirements for your application.

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.

12. Practical Assignment

Practice the following transaction tasks:

  1. Create two bank accounts with different balances.
  2. Transfer ₹500 from one account to another using a transaction.
  3. Verify that the total balance remains unchanged.
  4. Try transferring more money than the sender has.
  5. Confirm that an unsuccessful transfer does not debit the sender.
  6. Insert an order and a payment record in one transaction.
  7. Force an error after the first operation and verify that the transaction is aborted.
  8. Explain the difference between commit and abort.
  9. Explain why multi-document transactions require a supported deployment.

🧠 Quick Quiz

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?