A database transaction groups related reads and writes so they succeed together or fail together. If a travel booking reserves a flight but cannot reserve a hotel, aborting the transaction undoes the flight reservation rather than leaving a partial booking. section 8.0
In MongoDB, an operation on one document is already atomic: an insert, update, or delete completes fully or leaves that document unchanged. Use a multidocument transaction when one logical change must span documents or collections. WiredTiger uses optimistic concurrency control; conflicting writes can require a transaction to be retried. section 8.2 section 8.0
A practical transaction workflow is:
- Start a session and transaction. Set appropriate read and write concerns; the mongosh example uses snapshot reads, majority writes, and a primary read preference. section 8.4.2
- Perform the related operations in the transaction, then commit if they succeed or abort if one fails. section 8.4.2
- Handle transient failures. In mongosh, the example explicitly retries labeled errors and ends each session; driver Callback APIs automate starting, committing, and retrying. section 8.4.2 section 8.4.3
Coordinated account update
An account change can update an account document and related customer documents while inserting a transaction record. Grouping those writes prevents a failed step from leaving only some records updated. The Node.js driver runs the operations inside a session.withTransaction callback. section 8.4.2 section 8.4.3
Listing 8.2 Executing a multidocument transaction with Node.js and Callback API
const { MongoClient } = require('mongodb')
// Replace with your actual connection string
const uri = "your_mongodb_connection_string"
async function run(accountId) {
const client = new MongoClient(uri)
try {
await client.connect()
const session = client.startSession()
const transactionOptions = {
readConcern: { level: 'snapshot' },
writeConcern: { w: 'majority' },
readPreference: 'primary'
}
await session.withTransaction(async () => {
const accounts = client.db('sample_analytics').collection('accounts')
const customers = client.db('sample_analytics').collection('customers')
const transactions = client.db('sample_analytics').collection('transactions')
const currentDate = new Date()
const account = await accounts.findOne(
{ account_id: parseInt(accountId) },
{ session }
)
if (!account) throw new Error('Account not found')
const accountsUpdateResult = await accounts.updateOne(
{ account_id: parseInt(accountId) },
{
$set: { limit: 12000, last_transaction_date: currentDate },
$inc: { transaction_count: 1 }
},
{ session }
)
const customersUpdateResult = await customers.updateMany(
{ accounts: { $in: [parseInt(accountId)] } },
{
$inc: { transaction_count: 1 },
$set: { last_transaction_date: currentDate }
},
{ session }
)
const transactionsInsertResult = await transactions.insertOne(
{
account_id: parseInt(accountId),
transaction_count: account.transaction_count + 1,
bucket_start_date: currentDate,
bucket_end_date: currentDate,
transactions: [
{
date: currentDate,
amount: 1500,
transaction_code: 'buy',
symbol: 'amzn',
price: '125.00',
total: '187500.00'
}
]
},
{ session }
)
console.log("Transaction committed.")
console.log("Accounts updated:", accountsUpdateResult.modifiedCount)
console.log("Customers updated:", customersUpdateResult.modifiedCount)
console.log("New transaction inserted:", transactionsInsertResult.insertedId)
}, transactionOptions)
session.endSession()
} catch (error) {
console.error("Transaction aborted due to error:", error)
} finally {
await client.close()
}
}
// Get accountId from command line arguments
const accountId = process.argv[2]
if (!accountId) {
console.error("Please provide an account ID as an argument.")
process.exit(1)
}
run(accountId).catch(console.dir)
The Node.js listing shows the client session, transaction options, and withTransaction callback together. section 8.4.3
Do not use distributed transactions as a substitute for data modeling. Keeping related data together in embedded documents or arrays can avoid their performance cost. For transactions that remain necessary, use indexes, keep them short, plan error handling and retries, and account for added overhead across shards. The guidance recommends modifying no more than 1,000 documents per transaction and notes a default 60-second timeout. Some operations, including certain collection and index creation cases, are restricted within transactions. section 8.4.3 section 8.5













