Overview
在本指南中,您可以了解如何使用 Node.js 驱动程序执行事务。事务允许您运行一系列操作,这些操作在提交整个事务之前不会更改任何数据。如果事务中的任何操作失败,驱动程序就会结束事务,并在所有数据更改变得可见之前予以丢弃。这种特征称为原子性。
由于 MongoDB 中对单个文档进行的所有写操作都是原子操作,因此您可能希望使用事务来进行修改多个文档的原子更改。这种情况需要进行多文档事务。多文档事务符合 ACID,因为 MongoDB 会为您的事务操作中涉及的数据保证一致性,即使驱动程序遇到意外错误。
要了解有关 ACID 合规和事务的更多信息,请参阅我们有关 ACID 事务的文章。
注意
要执行多文档事务,必须连接到运行 MongoDB Server 4.0 或更高版本的部署。
有关限制的详细列表,请参阅服务器手册中的事务和操作部分。
有关如何使用驱动程序执行多文档事务的更多信息,请参阅本指南的以下章节:
因果一致性(Causal Consistency)
MongoDB在某些客户端会话中实现因果一致性。因果一致性模型ACID 一致性保证在分布式系统中,会话中的操作按因果顺序运行。客户端观察到的结果与因果关系或操作之间的依赖关系一致。示例,如果您执行一系列操作,其中一个操作在逻辑上依赖于另一个操作的结果,则任何后续读取都会反映这种依赖关系。
为了保证因果一致性,客户端端会话必须满足以下要求:
启动会话时,驾驶员必须启用因果一致性选项。该选项默认启用。
操作必须在单个线程的单个会话中运行。否则,会话或线程必须相互传达optime和集群时间值。 要查看传达这些值的两个会话的示例,请参阅MongoDB Server手册中的因果一致性示例。
您必须使用
majority
读关注(read concern)。您必须使用
majority
写关注(write concern)。这是默认的写关注(write concern)值。
下表描述了因果一致会话提供的ACID 一致性保证:
保证 | 说明 |
---|---|
读取写入操作 | 读取操作会反映之前写入操作的结果。 |
单调读取 | 读取操作不会返回反映比先前读取操作更早的数据状态的结果。 |
单调写入 | 如果写入操作必须先于其他写入操作,则服务器会先运行此写入操作。 示例,如果调用 |
读取后写入 | 如果写入操作必须在其他读取操作之后执行,服务器会先执行读取操作。 示例,如果您调用 |
提示
要学习;了解有关本节中提到的概念的更多信息,请参阅以下MongoDB Server手册条目:
事务 API
该驱动程序提供了两个用于执行事务的 API:核心 API和便捷事务 API。
借助 Core API 框架,您可以创建、提交和结束事务。使用该 API 时,必须显式执行以下操作:
创建、提交和结束事务。
创建并结束运行事务的会话。
实施错误处理逻辑。
借助便捷事务 API 框架,您能够执行事务,而无需负责提交或结束事务。当服务器引发某些错误类型时,该 API 会自动纳入错误处理逻辑以重新尝试操作。如需有关此行为的更多信息,请参阅本指南的“事务错误”部分。
重要
对于 MongoDB Server 4.2 及更早版本,您只能在事务中对已存在的集合执行写操作。对于 MongoDB Server 4.4 及更高版本,当您在事务中执行写操作时,服务器会根据需要自动创建集合。如需有关此行为的更多信息,请参阅服务器手册中的在事务中创建集合和索引。
Core API
Core API 提供以下方法来执行事务:
startSession() :创建新的
ClientSession
实例startTransaction():开始新事务
commitTransaction():在创建活动事务的会话中提交活动事务
abortTransaction():在创建事务的会话中结束活跃事务
endSession():结束活动会话
使用该 API 时,必须执行以下步骤:
将该会话实例传递给您想在该会话中运行的每个操作。
实现
catch
块,您可以在其中识别服务器事务错误并实现错误处理逻辑。
以下代码演示如何使用 Core API 执行事务:
async function coreTest(client) { const session = client.startSession(); try { session.startTransaction(); const savingsColl = client.db("bank").collection("savings_accounts"); await savingsColl.findOneAndUpdate( {account_id: "9876"}, {$inc: {amount: -100 }}, { session }); const checkingColl = client.db("bank").collection("checking_accounts"); await checkingColl.findOneAndUpdate( {account_id: "9876"}, {$inc: {amount: 100 }}, { session }); // ... perform other operations await session.commitTransaction(); console.log("Transaction committed."); } catch (error) { console.log("An error occurred during the transaction:" + error); await session.abortTransaction(); } finally { await session.endSession(); } }
重要
与启动会话的客户端一起使用会话
如果您提供从一个 MongoClient
实例到另一个客户端实例的会话,则驱动程序将抛出错误。
例如,以下代码会生成 MongoInvalidArgumentError
错误,因为它会从 client1
客户端创建 ClientSession
实例,但也会将此会话提供给 client2
客户端进行写入操作:
const session = client1.startSession(); client2.db('myDB').collection('myColl').insertOne({ name: 'Jane Eyre' }, { session });
提示
显式资源管理
Node.js驾驶员本身支持对MongoClient
、ClientSession
、ChangeStreams
和游标进行显式资源管理。此功能为实验性功能,可能会发生更改。要学习;了解如何使用显式资源管理,请参阅 v6.9 发布说明。
要查看使用此API的完全可运行的示例,请参阅使用 Core API用法示例。
便捷事务 API
便捷事务 API 提供以下实施事务的方法:
withSession():运行会话中传递给它的回调。API 会自动处理会话的创建和终止。
withTransaction():运行事务中传递给它的回调,并在回调返回时调用
commitTransaction()
方法。
这些方法返回 回调 返回的值。例如,如果您传递给 withTransaction()
方法的 回调 返回文档 { hello: "world" }
,则 withTransaction()
方法也会返回该文档。
重要
为避免无限循环错误,请确保传递给 withTransaction()
方法的回调捕获它引发的任何错误。
使用便捷事务 API 时,您可以将回调的返回值作为 withTransaction()
和 withSession()
方法的返回值传播,以便在代码中的其他位置使用它们。
使用该 API 时,必须执行以下步骤:
将该会话实例传递给您想在该会话中运行的每个操作。
为会话中的每个操作执行异步
await
事务语法。避免并行,例如调用
Promise.all()
方法。并行使用会话,通常会导致服务器错误。
以下代码演示如何使用 Convenient Transaction API 执行事务:
async function convTest(client) { let txnRes = await client.withSession(async (session) => session.withTransaction(async (session) => { const savingsColl = client.db("bank").collection("savings_accounts"); await savingsColl.findOneAndUpdate( {account_id: "9876"}, {$inc: {amount: -100 }}, { session }); const checkingColl = client.db("bank").collection("checking_accounts"); await checkingColl.findOneAndUpdate( {account_id: "9876"}, {$inc: {amount: 100 }}, { session }); // ... perform other operations return "Transaction committed."; }, null) ); console.log(txnRes); }
要查看使用此 API 完全可运行的示例,请参阅使用便捷事务 API 用法示例。
注意
不支持并行操作
Node.js 驱动程序不支持在单个事务中运行并行操作。
事务选项
可以将 TransactionOptions
实例传递给 startTransaction()
和 withTransaction()
方法,来配置驱动程序执行事务的方式。指定一个选项后,该选项会覆盖您可能在 MongoClient
实例上设置的选项值。
以下表格包括可以在 TransactionOptions
实例中指定的选项:
设置 | 说明 |
---|---|
| Specifies read operation consistency of the replica set. To learn more, see Read Concern in the Server manual. |
| Specifies the write operation level of acknowledgment required
from a replica set. To learn more, see Write Concern in the Server manual. |
| Specifies how to route read operations to members of a replica set. To learn more, see Read Preference in the Server manual. |
| 指定对事务的提交动作可以运行的时间长度,以毫秒为单位。 |
有关选项的完整列表,请参阅 TransactionOptions 的 API 文档。
注意
事务从您的 MongoClient
实例继承设置,除非您在事务选项中指定设置。
以下代码展示了如何定义事务选项并将其传递给 startTransaction()
方法:
const txnOpts = { readPreference: 'primary', readConcern: { level: 'local' }, writeConcern: { w: 'majority' }, maxCommitTimeMS: 1000 }; session.startTransaction(txnOpts);
事务错误
由于MongoDB事务符合ACID ,因此驾驶员可能会在运行期间产生错误,以确保数据保持一致。如果出现以下错误,您的应用程序必须重试ACID 事务:
TransientTransactionError
:如果写入操作在驾驶员提交ACID 事务之前遇到错误,则会引发此错误。要学习;了解有关此错误类型的更多信息,请参阅服务器手册中驱动程序API页面上的 TransientTransactionError 描述。UnknownTransactionCommitResult
:如果提交操作遇到错误,则引发此错误。要学习;了解有关此错误类型的更多信息,请参阅服务器手册中驱动程序API页面上的 UnknownTransactionCommitResult 描述。
以下部分介绍了在使用不同 API 时如何处理这些错误。
便捷事务 API错误处理
便捷事务 API包含针对这些错误类型的重试逻辑。驾驶员会自动重试ACID 事务,直到成功提交。
核心API错误处理
如果使用 Core API执行ACID 事务,则必须在应用程序中添加以下错误处理函数:
一个函数,当驾驶员遇到以下情况时,该函数会重试整个ACID 事务:
TransientTransactionError
一个函数,当驾驶员遇到以下情况时,该函数会重试提交操作:
UnknownTransactionCommitResult
这些函数必须运行,直到成功提交或出现其他错误。有关此重试逻辑的示例,请参阅服务器手册中驱动程序API页面上的核心API部分。