Sling Academy
Home/MongoDB/Understanding Transactions in MongoDB (with examples)

Understanding Transactions in MongoDB (with examples)

Last updated: February 03, 2024

Introduction

Transactions are crucial for maintaining data integrity in applications that require operations involving multiple documents or collections to be atomic. In MongoDB, transactions were introduced in version 4.0, offering a way to perform multi-document read and write operations atomically. This means you can apply all operations successfully or rollback changes in case of an error, similar to traditional relational databases.

This tutorial will guide you through the concept of transactions in MongoDB with a variety of examples to facilitate understanding—from the basics to more advanced concepts.

Prerequisites

  • Familiarity with basic MongoDB operations and query language.
  • MongoDB server version 4.0 or later installed and running.
  • MongoDB driver or MongoDB Shell (mongosh) ready for operation.

Setting Up Your Environment

Before diving into transactions, make sure your MongoDB instance is running, and you are connected to it using either a MongoDB driver for your preferred programming language or the MongoDB Shell. Ensure that you are connecting to a replica set, as transactions are not supported on standalone instances.

# Connecting to a MongoDB replica set using the MongoDB Shell
mongosh "mongodb://localhost:27017/?replicaSet=myReplicaSet"

Basic Transactions

To start a basic transaction:

const session = db.getMongo().startSession();
session.startTransaction();
try {
    // Perform operations within the transaction
    session.getDatabase('myDatabase').collection('orders').insertOne({ item: 'book', quantity: 10 });
    session.getDatabase('myDatabase').collection('inventory').updateOne({ item: 'book' }, { $inc: { quantity: -10 } });
    // Commit the transaction
    session.commitTransaction();
} catch (error) {
    // Abort the transaction on any error
    session.abortTransaction();
    throw error;
} finally {
    // End the session
    session.endSession();
}

In the example above, we inserted an order into the ‘orders’ collection and adjusted the inventory in the ‘inventory’ collection. If anything goes wrong in either operation, the entire transaction is rolled back.

Handling Errors and Retrying

It’s important to handle errors correctly and implement a retry mechanism:

const executeTransaction = async (session) => {
    while (true) {
        try {
            // Start a new transaction
            session.startTransaction();
            // Perform desired operations within the transaction
            // ... (transactional code goes here)
            // Attempt to commit the transaction
            await session.commitTransaction();
            console.log('Transaction committed.');
            break; // Exit the loop on success
        } catch (error) {
            if (error.hasErrorLabel('TransientTransactionError')) {
                // Retry the whole transaction
                console.log('TransientTransactionError, retrying transaction ...');
                continue;
            } else {
                // Cannot retry the transaction
                console.error('Error during transaction, aborting ...');
                await session.abortTransaction();
                throw error;
            }
        }
    }
};

This piece of code demonstrates the best practice of handling a TransientTransactionError by retrying the transaction, while aborting it on other errors.

Advanced Transactions and Concurrency

With more advanced use cases, you may encounter concurrency and isolation issues. Let’s explore how to handle subsequent transactions that must be queued:

// A hypothetical function to queue transactions
const transactionQueue = [];
const processNextTransaction = async (session) => {
    if (transactionQueue.length === 0) return;
    const nextTransaction = transactionQueue.shift();
    await nextTransaction(session);
    await processNextTransaction(session);
};

This simple queue ensures that transactions are processed in order, avoiding conflicts between concurrent operations.

Real-world Use Cases

Oftentimes, financial applications call for complex transactions. The next example simulates transferring funds from one account to another:

const transferFunds = async (fromAccountId, toAccountId, amount) => {
    const session = db.getMongo().startSession();
    try {
        session.startTransaction();
        // Withdraw funds from the 'fromAccount'
        await session.getDatabase('finances').collection('accounts').updateOne({ _id: fromAccountId }, { $inc: { balance: -amount } });
        // Deposit amounts into the 'toAccount'
        await session.getDatabase('finances').collection('accounts').updateOne({ _id: toAccountId }, { $inc: { balance: amount } });
        await session.commitTransaction();
        console.log('Funds transferred successfully.');
    } catch (error) {
        console.error('Failed to transfer funds:', error);
        await session.abortTransaction();
    } finally {
        session.endSession();
    }
};

In this example, the ‘withdraw’ and ‘deposit’ operations must both succeed, or no funds are transferred at all, ensuring the atomicity of the transaction.

Conclusion

MongoDB transactions provide a powerful mechanism to ensure data integrity in complex applications. They allow developers to apply multiple operations atomically, and with careful error handling, your databases can maintain consistent state even in the event of unforeseen errors. By following the practices outlined in this tutorial, you will be better prepared to implement transactions safely and efficiently in your MongoDB-based applications.

Next Article: Type checking in MongoDB: A practical guide (with examples)

Previous Article: Working with Change Streams in MongoDB (with examples)

Series: MongoDB Tutorials

MongoDB

You May Also Like

  • MongoDB: How to combine data from 2 collections into one
  • Hashed Indexes in MongoDB: A Practical Guide
  • Partitioning and Sharding in MongoDB: A Practical Guide (with Examples)
  • Geospatial Indexes in MongoDB: How to Speed Up Geospatial Queries
  • Understanding Partial Indexes in MongoDB
  • Exploring Sparse Indexes in MongoDB (with Examples)
  • Using Wildcard Indexes in MongoDB: An In-Depth Guide
  • Matching binary values in MongoDB: A practical guide (with examples)
  • Understanding $slice operator in MongoDB (with examples)
  • Caching in MongoDB: A practical guide (with examples)
  • CannotReuseObject Error: Attempted illegal reuse of a Mongo object in the same process space
  • How to perform cascade deletion in MongoDB (with examples)
  • MongoDB: Using $not and $nor operators to negate a query
  • MongoDB: Find SUM/MIN/MAX/AVG of each group in a collection
  • References (Manual Linking) in MongoDB: A Developer’s Guide (with Examples)
  • MongoDB: How to see all fields in a collection (with examples)
  • Type checking in MongoDB: A practical guide (with examples)
  • How to query an array of subdocuments in MongoDB (with examples)
  • MongoDB: How to compare 2 documents (with examples)