Transactions事务
On this page本页内容
Overview概述
Read this guide to learn how to perform transactions in MongoDB using the Node.js driver. 阅读本指南,了解如何使用Node.js驱动程序在MongoDB中执行事务。A transaction is a unit of work, composed of a series of operations that you want either to succeed together, or fail together when one or more of the operations fail. 事务是一个工作单元,由一系列操作组成,您希望这些操作一起成功,或者在一个或多个操作失败时一起失败。This behavior is called atomicity. 这种行为称为原子性。Atomicity is a property in which transactions composed of one or more operations occur all at once, such that no other client can observe them as separate operations, and that it leaves no changes if one of the operations fails.原子性是一种属性,其中由一个或多个操作组成的事务同时发生,因此没有其他客户端可以将它们视为单独的操作,并且如果其中一个操作失败,它不会留下任何更改。
Since all write operations on a single document in MongoDB are atomic, you may benefit most from transactions when you must make an atomic change that modifies multiple documents, which is called a multi-document transaction. 由于MongoDB中对单个文档的所有写入操作都是原子操作,因此当您必须进行修改多个文档的原子更改时,您可能会从事务中受益最多,这被称为多文档事务。Similar to write operations on a single document, multi-document transactions are ACID compliant, which means MongoDB guarantees the data involved in your transaction operations remains consistent, even if it encounters unexpected errors. 与对单个文档的写操作类似,多文档事务是ACID兼容的,这意味着MongoDB保证事务操作中涉及的数据保持一致,即使遇到意外错误。Learn more from this MongoDB article on ACID transactions.从这篇关于ACID事务的MongoDB文章中了解更多信息。
You can use the driver to perform multi-document transactions.您可以使用驱动程序执行多文档事务。
To run multi-document transactions, you must use MongoDB version 4.0 or later.要运行多文档事务,必须使用MongoDB 4.0或更高版本。
For a detailed list of limitations, see the Transactions and Operations section in the server manual.有关限制的详细列表,请参阅服务器手册中的事务和操作部分。
In MongoDB, multi-document transactions run within a client session. 在MongoDB中,多文档事务在客户端会话中运行。A client session is a grouping of related read or write operations that you want to ensure run sequentially. 客户端会话是一组相关的读或写操作,您希望确保这些操作按顺序运行。We recommend you reuse your client for multiple sessions and transactions instead of instantiating a new client each time.我们建议您为多个会话和事务重用客户端,而不是每次实例化一个新客户端。
When combined with majority read and write concerns, the driver can guarantee causal consistency between the operations. 当与大多数读写问题相结合时,驱动程序可以保证操作之间的因果一致性。See the server manual guide on Client Sessions and Causal Consistency Guarantees for more information.有关详细信息,请参阅有关客户端会话和因果一致性保证的服务器手册指南。
Learn more about how to use the driver to run multi-document transactions on MongoDB in the following sections of this guide:在本指南的以下部分中,了解有关如何使用驱动程序在MongoDB上运行多文档事务的更多信息:
Transaction APIs事务API
Use the Core API to implement a transaction with the driver. 使用核心API实现带有驱动程序的事务。To use the Core API, you declare the start and commit points of the transaction.要使用核心API,需要声明事务的开始点和提交点。
Core API
The Core API features methods to start, cancel, or commit a transaction. 核心API具有启动、取消或提交事务的方法。When you commit the transaction, you send a request to the server to make the changes from your operations atomically. 当您提交事务时,您会向服务器发送一个请求,以原子方式对操作进行更改。When using this API, you must handle certain transaction errors returned by the server manually.使用此API时,必须手动处理服务器返回的某些事务错误。
See TransientTransactionError and UnknownTransactionCommitResult for more information on these errors.有关这些错误的详细信息,请参阅TransientTransactionError
和UnknownTransactionCommitResult
。
To start, cancel, or commit your transaction, you can call the corresponding method on the 要启动、取消或提交事务,可以在Session
object:Session
对象上调用相应的方法:
startTransaction()
commitTransaction()
abortTransaction()
See the Core API example for a sample transaction implementation.有关示例事务实现,请参阅核心API示例。
Transaction Settings事务设置
When you instantiate a transaction, you can specify the following options to set the default behavior for that transaction:实例化事务时,可以指定以下选项来设置该事务的默认行为:
readConcern | |
writeConcern | |
readPreference | |
maxCommitTimeMS |
If you do not provide values, the driver uses the client settings.如果不提供值,则驱动程序将使用客户端设置。
You can specify the transaction options in the Core API using code that resembles the following:您可以在Core API中使用类似以下代码指定事务选项:
const transactionOptions = {
readPreference: 'primary',
readConcern: { level: 'local' },
writeConcern: { w: 'majority' },
maxCommitTimeMS: 1000
};
session.startTransaction(transactionOptions);
Examples实例
Consider a scenario in which a customer purchases items from your online store. 考虑一个场景,客户从您的在线商店购买商品。To record the purchase, your application needs to update information related to inventory, the customer's orders, and register the order details. 要记录购买,您的应用程序需要更新与inventory
、客户订单相关的信息,并注册订单详细信息。Suppose you organize the data updates as follows:假设您按如下方式组织数据更新:
orders | insert | |
customers | update | |
inventory | update |
A purchase can fail several ways such as if there's insufficient quantity of the item in inventory, if the order couldn't be completed, or if your payment system is offline.采购可能会以多种方式失败,例如inventory
中的商品数量不足、订单无法完成或您的支付系统离线。
If the payment fails, you can use a transaction to make sure that you avoid exposing any partial updates that might cause data consistency issues for other operations that depend on that data.如果付款失败,您可以使用事务来确保避免暴露任何可能导致依赖该数据的其他操作的数据一致性问题的部分更新。
Sample Data示例数据
The code examples require the following sample data in the 代码示例需要testdb
database to run the multi-document payment transaction:testdb
数据库中的以下示例数据来运行多文档支付事务:
A document in thecustomers
collection that describes a customer and their orders.customers
集合中描述客户及其订单的文档。Documents in theinventory
collection that each track quantity and description of an item.inventory
集合中的文档,每个文档跟踪物料的数量和描述。
The document in the 本例中customers
collection in this example contains the following:customers
集合中的文档包含以下内容:
{ _id: 98765, orders: [] }
The documents in the 本例中inventory
collection in this example contain the following:inventory
集合中的文档包含以下内容:
[
{ name: "sunblock", sku: 5432, qty: 85 },
{ name: "beach towel", sku: 7865, qty: 41 }
]
The code examples also perform operations on the 代码示例还对orders
collection, but do not require any prior sample documents.orders
集合执行操作,但不需要任何先前的示例文档。
The code examples use the 代码示例使用cart
and payment
variables to represent a sample list of items purchased and the order payment details as follows:cart
和payment
变量来表示所购买物品的示例列表和订单付款详细信息,如下所示:
const cart = [
{ name: 'sunblock', sku: 5432, qty: 1, price: 5.19 },
{ name: 'beach towel', sku: 7865, qty: 2, price: 15.99 }
];
const payment = { customer: 98765, total: 37.17 };
The examples in the following sections require that you create the collections outside of the transaction or that you are using MongoDB 4.4 or later. For more information on creating collections inside a transaction, see the Create Collections and Indexes in a Transaction server guide.以下部分中的示例要求您在事务之外创建集合,或者使用MongoDB 4.4或更高版本。有关在事务中创建集合的更多信息,请参阅在事务服务器中创建集合和索引指南。
Core API Implementation核心API实现
The code example in this section demonstrates how you can use the Core API to run the multi-document payment transaction in a session. 本节中的代码示例演示了如何使用Core API在会话中运行多文档支付事务。This function shows how you can perform the following:此功能显示如何执行以下操作:
Start a session启动会话Start a transaction, specifying transaction options启动事务,指定事务选项Perform data operations in the same session在同一会话中执行数据操作Commit a transaction, or cancel it if the driver encounters an error提交事务,或者在驱动程序遇到错误时取消事务End a session结束会话
async function placeOrder(client, cart, payment) {
const transactionOptions = {
readConcern: { level: 'snapshot' },
writeConcern: { w: 'majority' },
readPreference: 'primary'
};
const session = client.startSession();
try {
session.startTransaction(transactionOptions);
const ordersCollection = client.db('testdb').collection('orders');
const orderResult = await ordersCollection.insertOne(
{
customer: payment.customer,
items: cart,
total: payment.total,
},
{ session }
);
const inventoryCollection = client.db('testdb').collection('inventory');
for (let i=0; i<cart.length; i++) {
const item = cart[i];
//Cancel the transaction when you have insufficient inventory库存不足时取消事务
const checkInventory = await inventoryCollection.findOne(
{
sku: item.sku,
qty: { $gte: item.qty }
},
{ session }
)
if (checkInventory === null) {
throw new Error('Insufficient quantity or SKU not found.');
}
await inventoryCollection.updateOne(
{ sku: item.sku },
{ $inc: { 'qty': -item.qty }},
{ session }
);
}
const customerCollection = client.db('testdb').collection('customers');
await customerCollection.updateOne(
{ _id: payment.customer },
{ $push: { orders: orderResult.insertedId }},
{ session }
);
await session.commitTransaction();
console.log('Transaction successfully committed.');
} catch (error) {
if (error instanceof MongoError && error.hasErrorLabel('UnknownTransactionCommitResult')) {
//add your logic to retry or handle the error添加逻辑以重试或处理错误
}
else if (error instanceof MongoError && error.hasErrorLabel('TransientTransactionError')) {
//add your logic to retry or handle the error添加逻辑以重试或处理错误
} else {
console.log('An error occured in the transaction, performing a data rollback:' + error);
}
await session.abortTransaction();
} finally {
await session.endSession();
}
}
Note that you must pass the session object to each CRUD operation that you want to run on that session.请注意,必须将会话对象传递给要在该会话上运行的每个CRUD操作。
The code and comments in the catch
block demonstrate how you can identify the server transaction errors and where you can place your logic to handle them. catch
块中的代码和注释演示了如何识别服务器事务错误,以及如何放置逻辑来处理这些错误。Make sure to include the 请确保在代码中包含驱动程序中的MongoError
type from the driver in your code as shown in the following sample import statement:MongoError
类型,如以下示例导入语句所示:
const { MongoError, MongoClient } = require('mongodb');
See the Payment Transaction Result section to see what your collections should contain after you run the transaction.请参阅付款事务结果部分,查看运行事务后您的收款应包含哪些内容。
Payment Transaction Result付款事务结果
If your application completes the payment transaction, your database should contain all the updates, and if an exception interrupted your transaction, none of the changes should exist in your database.如果您的应用程序完成了支付事务,则您的数据库应包含所有更新,如果异常中断了您的事务,则数据库中不应存在任何更改。
The customers
collection should contain the customer document with an order id appended to the orders field:customers
集合应包含在订单字段后附加订单id的客户文档:
{
"_id": 98765,
"orders": [
"61dc..."
]
}
The inventory
collection should contain updated quantities for the items "sunblock" and "beach towel":inventory
清单应包含“防晒霜”和“沙滩巾”的最新数量:
[
{
"_id": ...,
"name": "sunblock",
"sku": 5432,
"qty": 84
},
{
"_id": ...,
"name": "beach towel",
"sku": 7865,
"qty": 39
}
]
The orders
collection should contain the order and payment information:orders
集合应包含订单和付款信息:
[
{
"_id": "...",
"customer": 98765,
"items": [
{
"name": "sunblock",
"sku": 5432,
"qty": 1,
"price": 5.19
},
{
"name": "beach towel",
"sku": 7865,
"qty": 2,
"price": 15.99
}
],
"total": 37.17
}
]