事务
Overview
在本指南中,您可以了解如何使用 Node.js 驱动程序执行事务。事务允许您运行一系列操作,这些操作在提交整个事务之前不会更改任何数据。如果事务中的任何操作失败,驱动程序就会结束事务,并在所有数据更改变得可见之前予以丢弃。这种特征称为原子性。
由于 MongoDB 中对单个文档进行的所有写操作都是原子操作,因此您可能希望使用事务来进行修改多个文档的原子更改。这种情况需要进行多文档事务。多文档事务符合 ACID,因为 MongoDB 会为您的事务操作中涉及的数据保证一致性,即使驱动程序遇到意外错误。
要了解有关 ACID 合规和事务的更多信息,请参阅我们有关 ACID 事务的文章。
注意
要执行多文档事务,必须连接到运行 MongoDB Server 4.0 或更高版本的部署。
有关限制的详细列表,请参阅服务器手册中的事务和操作部分。
在 MongoDB 中,多文档事务在客户端会话中运行。客户端会话是要按顺序执行的一组相关读取或写入操作。我们建议您将客户端重复用于多个会话和事务,而不是每次都实例化一个新客户端。
当与 majority
读关注和写关注结合使用时,该驱动程序可保证操作之间的因果一致性。要了解更多信息,请参阅服务器手册中的客户端会话和因果一致性保证。
有关如何使用驱动程序执行多文档事务的更多信息,请参阅本指南的以下章节:
事务 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 });
要查看使用此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驾驶员不支持在单个ACID 事务中运行并行操作。
事务选项
可以将 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);
事务错误
如果使用 Core API 执行事务,则必须将错误处理逻辑加入应用程序中,用于处理以下错误:
TransientTransactionError
:如果在驱动程序提交事务之前写操作出错,则会抛出错误。要了解有关此错误的更多信息,请参阅服务器手册 Driver API 页面上的 TransientTransactionError 说明。UnknownTransactionCommitResult
:如果提交操作遇到错误,则引发此错误。要了解有关此错误的更多信息,请参阅服务器手册驱动程序 API 页面上的 UnknownTransactionCommitResult 说明。
便捷事务 API 集成了针对这些错误类型的重试逻辑,因此该驱动程序会重试此事务,直到成功提交。