Navigation

Transactions

For situations that require atomicity of reads and writes to multiple documents (in one or more collections), MongoDB supports multi-document transactions. The code snippets on this page demonstrate how to execute multiple MongoDB queries in a single transaction that will undo changes in the event of an error.

To use a code snippet in a function, you must first instantiate a MongoDB collection handle:

exports = function() {
const mongodb = context.services.get("mongodb-atlas");
const itemsCollection = mongodb.db("store").collection("items");
const purchasesCollection = mongodb.db("store").collection("purchases");
// ... paste snippet here ...
}

To perform a transaction:

  1. Obtain and start a client session with client.startSession().
  2. (Optional) Define a read and write settings object for the transaction. Pass this object to the method called in the next step.
  3. Use the session.withTransaction() method to start a transaction.
  4. Execute the MongoDB queries that you would like to include in your transaction in the callback for this function, which needs to either be async or return a Promise. Be sure to pass the session to each query to ensure that it is included in the transaction.
  5. If the callback encounters an error, you should abort the transaction with session.abortTransaction() in a Promise rejection handler with Promise.catch() or a try ... catch exception handler.
  6. When the transaction is complete, call session.endSession() to end the session and free resources.

The following example creates two users, "henry" and "michelle", and a uses a transaction to move "browniePoints" between those users atomically:

exports = function () {
const client = context.services.get("mongodb-atlas");
db = client.db("exampleDatabase");
accounts = db.collection("accounts");
browniePointsTrades = db.collection("browniePointsTrades");
// create user accounts with initial balances
accounts.insertOne({ name: "henry", browniePoints: 42 });
accounts.insertOne({ name: "michelle", browniePoints: 144 });
// trade points between user accounts in a transaction
tradeBrowniePoints(
client,
accounts,
browniePointsTrades,
"michelle",
"henry",
5
);
return "Successfully traded brownie points.";
};
async function tradeBrowniePoints( client, accounts, browniePointsTrades, userAddPoints, userSubtractPoints, pointVolume ) {
// Step 1: Start a Client Session
const session = client.startSession();
// Step 2: Optional. Define options to use for the transaction
const transactionOptions = {
readPreference: "primary",
readConcern: { level: "local" },
writeConcern: { w: "majority" },
};
// Step 3: Use withTransaction to start a transaction, execute the callback, and commit (or abort on error)
// Note: The callback for withTransaction MUST be async and/or return a Promise.
try {
await session.withTransaction(async () => {
// Step 4: Execute the queries you would like to include in one atomic transaction
// Important:: You must pass the session to the operations
await accounts.updateOne(
{ name: userSubtractPoints },
{ $inc: { browniePoints: -1 * pointVolume } },
{ session }
);
await accounts.updateOne(
{ name: userAddPoints },
{ $inc: { browniePoints: pointVolume } },
{ session }
);
await browniePointsTrades.insertOne(
{
userAddPoints: userAddPoints,
userSubtractPoints: userSubtractPoints,
pointVolume: pointVolume,
},
{ session }
);
}, transactionOptions);
} catch (err) {
// Step 5: Handle errors with a transaction abort
await session.abortTransaction();
} finally {
// Step 6: End the session when you complete the transaction
await session.endSession();
}
}
Give Feedback