ACID transaction support (single-document and multi-document transactions)
Basic Concepts of ACID Transactions
ACID refers to the four key properties of database transactions: Atomicity, Consistency, Isolation, and Durability. In MongoDB, transaction support has evolved from single-document to multi-document capabilities. Prior to version 4.0, MongoDB guaranteed ACID properties only at the single-document level. Starting with version 4.0, MongoDB introduced multi-document transaction support, enabling cross-collection and cross-document operations to also meet ACID requirements.
// Single-document transaction example
db.products.updateOne(
{ _id: 1, stock: { $gte: 1 } },
{ $inc: { stock: -1 }, $set: { lastModified: new Date() } }
)
Implementation of Single-Document Transactions
MongoDB inherently supports ACID properties at the single-document level. When performing a write operation on a single document, MongoDB ensures the operation either fully succeeds or completely fails, with no partial updates. This atomicity is achieved through document-level locking mechanisms.
Typical use cases for single-document transactions include:
- Updating multiple fields within a document
- Adding or removing elements from an array
- Modifying the contents of nested documents
// Complex single-document transaction example
db.accounts.updateOne(
{ _id: "account1" },
{
$inc: { balance: -100 },
$push: { transactions: { amount: 100, date: new Date(), type: "withdrawal" } }
}
)
Evolution of Multi-Document Transactions
MongoDB 4.0 introduced support for multi-document ACID transactions, initially only for replica set deployments. Version 4.2 extended this to sharded clusters. Multi-document transactions allow operations across multiple documents, collections, or even databases within a single transaction.
Key features of multi-document transactions:
- Support for read preference and write concern
- Default maximum transaction duration of 60 seconds (configurable)
- Default isolation level of snapshot isolation
// Multi-document transaction example
const session = db.getMongo().startSession();
session.startTransaction({
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" }
});
try {
const accounts = session.getDatabase("bank").accounts;
accounts.updateOne({ _id: "A" }, { $inc: { balance: -100 } });
accounts.updateOne({ _id: "B" }, { $inc: { balance: 100 } });
session.commitTransaction();
} catch (error) {
session.abortTransaction();
throw error;
} finally {
session.endSession();
}
Performance Considerations for Transactions
Using multi-document transactions incurs certain performance overheads, primarily:
- Increased system resource usage during transactions
- Need to maintain transaction state and logs
- Potential for lock contention
Best practice recommendations:
- Keep transactions as brief as possible
- Avoid time-consuming operations within transactions
- Set appropriate transaction timeout values
- Consider using retryable writes for simple transactions
// Transaction example with retry logic
function runTransactionWithRetry(txnFunc, session) {
while (true) {
try {
txnFunc(session);
break;
} catch (error) {
if (error.hasErrorLabel("TransientTransactionError")) {
continue;
}
throw error;
}
}
}
Transaction Isolation Levels
MongoDB provides two primary transaction isolation levels:
- Read uncommitted - Default level
- Snapshot isolation - Achieved by explicitly setting readConcern
Isolation level choices affect:
- Data consistency
- Concurrent performance
- Likelihood of dirty reads or phantom reads
// Transaction with snapshot isolation level
const session = db.getMongo().startSession();
session.startTransaction({
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" }
});
Transactions in Replica Sets and Sharded Clusters
In replica set environments, transaction behavior is relatively straightforward. However, in sharded clusters, transactions involve greater complexity:
- All shards participating in a transaction must belong to the same replica set
- Transactions cannot span multiple shard key ranges
- A coordinator is required to manage cross-shard transactions
Limitations of sharded cluster transactions:
- Cannot affect more than 1,000 documents
- Cannot run longer than the operation execution time limit
- Certain DDL operations cannot be performed within transactions
// Transaction example in a sharded cluster
const session = db.getMongo().startSession();
session.startTransaction();
try {
// Operations must be on the same shard
db.orders.insertOne({ _id: 1, item: "abc", price: 100 }, { session });
db.inventory.updateOne(
{ item: "abc", qty: { $gte: 1 } },
{ $inc: { qty: -1 } },
{ session }
);
session.commitTransaction();
} catch (error) {
session.abortTransaction();
throw error;
}
Transaction Error Handling
Proper handling of transaction errors is crucial for maintaining data consistency. Common transaction errors include:
- TransientTransactionError: Temporary error, usually retryable
- UnknownTransactionCommitResult: Commit outcome unknown
- NoSuchTransaction: Transaction does not exist
// Complete transaction error handling example
async function transferFunds(from, to, amount) {
const session = client.startSession();
try {
await session.withTransaction(async () => {
const fromAccount = await accounts.findOne({ _id: from }, { session });
if (fromAccount.balance < amount) {
throw new Error("Insufficient funds");
}
await accounts.updateOne(
{ _id: from },
{ $inc: { balance: -amount } },
{ session }
);
await accounts.updateOne(
{ _id: to },
{ $inc: { balance: amount } },
{ session }
);
});
} catch (error) {
if (error.errorLabels && error.errorLabels.includes("TransientTransactionError")) {
console.log("Transient error, retrying...");
return transferFunds(from, to, amount);
}
throw error;
} finally {
await session.endSession();
}
}
Transaction Monitoring and Diagnostics
MongoDB provides various tools for monitoring and analyzing transaction performance:
-
Database commands:
currentOp
: View running transactionsserverStatus
: Obtain transaction statistics
-
Log analysis:
- Transaction start/commit/abort records
- Lock acquisition and release information
-
Performance metrics:
- Transaction duration
- Transaction retry count
- Conflict rate
// View currently running transactions
db.adminCommand({
currentOp: true,
$or: [
{ op: "command", "command.abortTransaction": { $exists: true } },
{ op: "command", "command.commitTransaction": { $exists: true } },
{ op: "command", "command.startTransaction": { $exists: true } }
]
})
Transaction Best Practices
Based on production environment experience, consider the following best practices when using MongoDB transactions:
-
Schema design:
- Prefer embedded documents to minimize cross-document operations
- Consider using two-phase commit patterns for complex scenarios
-
Performance optimization:
- Place frequently updated fields at the beginning of documents
- Avoid collection scans within transactions
-
Application layer handling:
- Implement retry logic for transient errors
- Set reasonable timeout values
// Two-phase commit pattern example
const transaction = {
_id: "txn123",
state: "initial",
participants: [
{ resource: "accounts", id: "A", action: "withdraw", amount: 100 },
{ resource: "accounts", id: "B", action: "deposit", amount: 100 }
],
lastModified: new Date()
};
// Phase 1: Prepare
db.transactions.insertOne(transaction);
db.accounts.updateOne(
{ _id: "A", pendingTransactions: { $ne: "txn123" } },
{ $inc: { balance: -100 }, $push: { pendingTransactions: "txn123" } }
);
// Phase 2: Commit
db.transactions.updateOne(
{ _id: "txn123", state: "initial" },
{ $set: { state: "committed" } }
);
db.accounts.updateOne(
{ _id: "A" },
{ $pull: { pendingTransactions: "txn123" } }
);
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:网络调试工具
下一篇:事务的隔离级别与并发控制