What are Transactions?
A transaction is a group of operations that either all succeed or all fail. Think of it like a bank transfer: money leaves one account and enters another. You don't want one to happen without the other.
Without transactions, partial failures can leave your data in an inconsistent state. MongoDB transactions ensure that your multi-document operations are atomic.
Transactions were introduced in MongoDB 4.0 for replica sets and 4.2 for sharded clusters. They bring ACID guarantees to the document world.
Multi-Document Transactions
Multi-document transactions let you perform operations across multiple documents and collections as a single unit. If any operation fails, the entire transaction rolls back.
You start a transaction, perform your operations, and then commit. If something goes wrong at any point, you abort and nothing changes. It's like having an undo button for database operations.
Transactions work with replica sets and sharded clusters. They require a session to run, and each session can have one active transaction at a time.
const session = db.getMongo().startSession()
session.startTransaction()
const accounts = session.getDatabase("bank").accounts
accounts.updateOne(
{ _id: "account-A" },
{ $inc: { balance: -100 } }
)
accounts.updateOne(
{ _id: "account-B" },
{ $inc: { balance: 100 } }
)
session.commitTransaction()
session.endSession()
ACID Properties
ACID stands for Atomicity, Consistency, Isolation, and Durability. These four properties guarantee that your transactions are reliable and predictable.
Atomicity means all operations in the transaction either complete or none do. Consistency ensures your data follows all rules after the transaction. Isolation means concurrent transactions don't interfere with each other. Durability guarantees committed data survives system failures.
MongoDB's implementation of ACID is slightly different from traditional databases, but it covers the most common use cases. The default isolation level is snapshot isolation.
const session = db.getMongo().startSession()
session.startTransaction({ readConcern: { level: "snapshot" } })
try {
const orders = session.getDatabase("shop").orders
const inventory = session.getDatabase("shop").inventory
orders.insertOne({ productId: "item-1", quantity: 2, status: "pending" })
inventory.updateOne(
{ productId: "item-1", stock: { $gte: 2 } },
{ $inc: { stock: -2 } }
)
session.commitTransaction()
} catch (error) {
session.abortTransaction()
print("Transaction failed: " + error.message)
} finally {
session.endSession()
}
When to Use Transactions
Use transactions when you need to maintain data consistency across multiple documents. Financial operations, inventory management, and order processing are classic examples.
Don't use transactions for everything. They add overhead and reduce performance. Single-document operations in MongoDB are already atomic, so you don't need a transaction for those.
If your application can tolerate eventual consistency, skip transactions. They're a tool for when you absolutely need all-or-nothing guarantees.
const session = db.getMongo().startSession()
session.startTransaction()
try {
const db = session.getDatabase("store")
db.carts.updateOne(
{ userId: "user-1" },
{ $pull: { items: { productId: "item-5" } } }
)
db.orders.insertOne({
userId: "user-1",
items: [{ productId: "item-5", quantity: 1 }],
total: 29.99,
createdAt: new Date()
})
db.inventory.updateOne(
{ productId: "item-5" },
{ $inc: { stock: -1 } }
)
session.commitTransaction()
} catch (e) {
session.abortTransaction()
} finally {
session.endSession()
}