Menu
Share on

Introduction

Transactions are logical groups of processing in a database that encapsulate one or more operations such as reads and/or writes across multiple documents. Instead of being conducted in individual statements, the database is able to interpret and act on the group of operations as a cohesive unit. This helps ensure the consistency of the dataset over the course of many closely related statements.

In this guide, we'll begin by discussing what transactions are, when to use them in the document model, and how they work conceptually in MongoDB.

RELATED ON PRISMA.IO

Prisma's MongoDB connector has recently been promoted to general availability! With this change, you can use the Prisma Client to manage production MongoDB databases with confidence.

Join us in celebrating this milestone on April 25-29 with our virtual MongoDB Launch week. There will be exclusive workshops, opportunities for Atlas credits, great swag, and much more!

What are transactions?

Transactions are a way to group together and isolate multiple statements for processing as a single operation. Instead of executing each command individually as it is sent to the server, in a transaction, commands are bundled together and executed in a separate context than other requests.

Isolation is an important part of transactions. Within a transaction, the executed statements can only affect the environment within the transaction itself. From inside the transaction, statements can modify data and the results are immediately visible. From the outside, no changes are made until the transaction is committed, at which time all of the actions within the transaction become visible at once.

These features help databases achieve ACID compliance by providing atomicity and isolation. These together help the database maintain consistency. Furthermore, changes in transactions are not returned as successful until they are committed to non-volatile storage, which provides durability.

MongoDB has support for multi-document ACID transactions and distributed multi-document ACID transactions. By nature, the document model allows related data to be stored together in a single document. The document model, combined with atomic document updates, takes away the need for transactions in a majority of use cases. However, there are cases where multi-document, multi-collection MongoDB transactions are your best choice.

Database Transactions

When to use transactions in the document model

The document model inherently solves for a lot of the needs that transactions target. Nonetheless, there are some use cases that stand out for their need to utilize transactions even in the document model.

Applications that require transactions are typically those where values are exchanged between different parties. Some examples would be "System of Record" or "Line of Business" applications.

The benefits of multi-document transactions are tailored for systems that move funds around like banking applications or payment processing, supply chain or shipping systems where ownership of a good is transferred, or in e-commerce. These examples are generally packaging changes together across multiple streams and require strong consistency with the all or nothing approach transactions offer.

How do you use transactions?

MongoDB provides two APIs to use transactions. The first is the core API which has similar syntax to relational databases. The second, the callback API, is the recommended approach to using transactions in MongoDB.

The comparison of the two APIs is best summarized in the following table:

Core APICallback API
Requires an explicit call to start and to commit the transactionStarts a transaction, executes the specified operations, and commits (or aborts on error)
Does not automatically incorporate error-handling logic for TransientTransactionError and UnknownTransactionCommitResult but instead grants the ability to integrate custom error handling.Automatically incorporates error-handling logic for TransientTransactionError and UnknownTransactionCommmitResult.
Requires explicit logical session to be passed to API for the specific transaction.Requires explicit logical session to be passed to API for the specific transaction.
RELATED ON PRISMA.IO

If you are looking to get started working with MongoDB and Prisma, checkout our getting started from scratch guide or how to add to an existing project.

Creating a transaction session

Transactions are typically written and executed from external applications via one of the API methods with the application language's appropriate MongoDB driver.

For the sake of demonstration, we'll walk through the creation of a transaction via the MongoDB shell. We'll work with an example database and collection to help conceptualize how transactions work in practice.

Note: If a transactions session runs for more than 60 seconds after the initial startTransaction() method, MongoDB will automatically abort the operation. This occurs because transactions are typically conducted in applications that work automatically rather than a person issuing commands in the shell. The following steps are for conceptualizing a transaction session from start to finish.

To begin, we have a simple database called literature with a collection called authors. Running a quick find() query, we can see the document structure with the author name and a title of theirs.

db.authors.find()
[
{
_id: ObjectId("620397dd4b871fc65c193106"),
first_name: 'James',
last_name: 'Joyce',
title: 'Ulysses'
},
{
_id: ObjectId("620398016ed0bb9e23785973"),
first_name: 'William',
last_name: 'Gibson',
title: 'Neuromancer'
},
{
_id: ObjectId("6203981d6ed0bb9e23785974"),
first_name: 'George',
last_name: 'Orwell',
title: 'Homage to Catalonia'
},
{
_id: ObjectId("620398516ed0bb9e23785975"),
first_name: 'James',
last_name: 'Baldwin',
title: 'The Fire Next Time'
}
]

Now that we have our database, we can show the steps to work with a transaction directly in the MongoDB shell. We'll also demonstrate how a transaction being conducted in one session may not be detectable to an outside source.

First, we need to create a session, same as you would when using the API.

var session = db.getMongo().startSession()

This newly created session variable will store the session object.

The next step is starting the transaction by calling the startTransaction() method:

session.startTransaction(
{ "readConcern": { "level": "snapshot" },
"writeConcern": { "w": "majority }
}
)

The startTransaction() method has two options, readConcern and writeConcern. You can read in more detail about these options in MongoDB's documentation. We set the readConcern level to snapshot which returns data from a snapshot of majority committed data if the transaction commits with writeConcern "majority".

When you commit with w: "majority" write concern along with a read concern level of "snapshot", this guarantees that operations have a synchronized snapshot of majority-committed data. This configuration of write and read concern can be thought of as good defaults if no specific requirements.

The method will not return anything if successful, and if there is any error, then the transaction will need to be aborted which will be discussed shortly.

Working inside of the transaction session

Now that you have the transaction session started, you must run statements within the context of the session variable from the previous section.

It's helpful to represent your collection in a variable within the session to ease the syntax. You can do this as follows:

var authors = session.getDatabase('literature').getCollection('authors')

This newly created variable will work the as db.authors would when working in a shell outside of the transaction session. You can verify this by opening a second shell window, connecting to your cluster, and running db.authors.find(). Both statements will return the same documents.

Inside of the session, we now want to simulate what an external application might do and add a record to the database. We can do this in the following way to our authors collection:

authors.insertOne( {
"first_name": "Virginie",
"last_name": "Despentes",
"title": "Vernon Subutex") }
)

MongoDB will return a success:

{
acknowledged: true,
insertedId: ObjectId("6203a075c374636bc6976baa")
}

If we run authors.find() now in the session we'll return the previous results with our addition included:

[
{
_id: ObjectId("620397dd4b871fc65c193106"),
first_name: 'James',
last_name: 'Joyce',
best_title: 'Ulysses'
},
{
_id: ObjectId("620398016ed0bb9e23785973"),
first_name: 'William',
last_name: 'Gibson',
best_title: 'Neuromancer'
},
{
_id: ObjectId("6203981d6ed0bb9e23785974"),
first_name: 'George',
last_name: 'Orwell',
best_title: 'Homage to Catalonia'
},
{
_id: ObjectId("620398516ed0bb9e23785975"),
first_name: 'James',
last_name: 'Baldwin',
best_title: 'The Fire Next Time'
},
{
_id: ObjectId("6203a075c374636bc6976baa"),
first_name: 'Virginie',
last_name: 'Despentes',
best_title: 'Vernon Subutex'
}
]

Since this transaction session has still not been committed, we will not see it return the same result in a MongoDB shell existing outside of the session. To confirm, we run db.authors.find() in another MongoDB shell instance and we return the original documents.

[
{
_id: ObjectId("620397dd4b871fc65c193106"),
first_name: 'James',
last_name: 'Joyce',
best_title: 'Ulysses'
},
{
_id: ObjectId("620398016ed0bb9e23785973"),
first_name: 'William',
last_name: 'Gibson',
best_title: 'Neuromancer'
},
{
_id: ObjectId("6203981d6ed0bb9e23785974"),
first_name: 'George',
last_name: 'Orwell',
best_title: 'Homage to Catalonia'
},
{
_id: ObjectId("620398516ed0bb9e23785975"),
first_name: 'James',
last_name: 'Baldwin',
best_title: 'The Fire Next Time'
}
]

This difference demonstrates that the transaction is still currently in a state of ambiguity. It can still either succeed and be committed to the database or it could fail and be aborted. Either way, the database will end up in a consistent state. It will include the new record or incur a rollback to the same state as before the transaction session started.

We run the commitTransaction() method in the session to commit the transaction and bring the database to the new consistent state:

session.commitTransaction()

Now, the new record will exist in both instances, the MongoDB shell where the transaction session was taking place and the MongoDB shell outside of the transaction.

Aborting a transaction

Now that we have gone from starting a session all the way to committing it, we can explore the alternative result for a transaction.

This path begins the same and changes only at the commit point. If we want to discard the changes the transaction is making, then we use the abortTransaction() method as follows in the session shell:

session.abortTransaction()

This method cancels the transaction session and discards any potential changes. Instead of resulting in a new consistent state, the database rolls back the changes and stays the same as when the transaction session first started.

Conclusion

In this guide, we discussed what transactions are and what use cases transactions suit best in MongoDB. We also walked through the process of a transaction session conceptually in the MongoDB shell, so you can understand how an external application will operate.

Transactions are a fundamental need for relational databases, and are also needed for specific use cases in document model databases such as MongoDB. When it comes to the document model, the general recommendation is that data that is accessed together should be stored together. However, sometimes this cannot be the case, so it is important to know the basics of transactions.

RELATED ON PRISMA.IO

Prisma's MongoDB connector has recently been promoted to general availability! With this change, you can use the Prisma Client to manage production MongoDB databases with confidence.

Join us in celebrating this milestone on April 25-29 with our virtual MongoDB Launch week. There will be exclusive workshops, opportunities for Atlas credits, great swag, and much more!

About the Author(s)
Alex Emerich

Alex Emerich

Alex is your typical bird watching, hip-hop loving bookworm that also enjoys writing about databases. He currently lives in Berlin, where he can be seen walking through the city aimlessly like Leopold Bloom.