Overview
在本指南中,您可以了解如何使用 Node.js 驱动程序执行事务。事务允许您运行一系列操作,这些操作在提交整个事务之前不会更改任何数据。如果事务中的任何操作失败,驱动程序就会结束事务,并在所有数据更改变得可见之前予以丢弃。这种特征称为原子性。
由于 MongoDB 中对单个文档进行的所有写操作都是原子操作,因此您可能希望使用事务来进行修改多个文档的原子更改。这种情况需要进行多文档事务。多文档事务符合 ACID,因为 MongoDB 会为您的事务操作中涉及的数据保证一致性,即使驱动程序遇到意外错误。
要了解有关 ACID 合规和事务的更多信息,请参阅我们的有关 ACID 事务的文章。
有关如何使用驱动程序执行多文档事务的更多信息,请参阅本指南的以下章节:
因果一致性(Causal Consistency)
MongoDB在某些客户端会话中实现因果一致性。因果一致性模型ACID 一致性保证在分布式系统中,会话中的操作按因果顺序运行。客户端观察到的结果与因果关系或操作之间的依赖关系一致。示例,如果您执行一系列操作,其中一个操作在逻辑上依赖于另一个操作的结果,则任何后续读取都会反映这种依赖关系。
为了保证因果一致性,客户端端会话必须满足以下要求:
启动会话时,驱动程序必须启用因果一致性选项。该选项默认启用。
操作必须在单个线程的单个会话中运行。否则,会话或线程必须相互传达optime和集群时间值。 要查看传达这些值的两个会话的示例,请参阅MongoDB Server手册中的因果一致性示例。
您必须使用
majority读关注(read concern)。您必须使用
majority写关注(write concern)。这是默认的写关注(write concern)值。
下表描述了因果一致会话提供的ACID 一致性保证:
保证 | 说明 |
|---|---|
读取写入操作 | 读取操作会反映之前写入操作的结果。 |
单调读取 | 读取操作不会返回反映比先前读取操作更早的数据状态的结果。 |
单调写入 | 如果写入操作必须先于其他写入操作,则服务器会先运行此写入操作。 示例,如果调用 |
读取后写入 | 如果写入操作必须在其他读取操作之后执行,服务器会先执行读取操作。 示例,如果您调用 |
事务 API
该驱动程序提供了两个用于执行事务的 API:核心 API和便捷事务 API。
借助 Core API 框架,您可以创建、提交和结束事务。使用该 API 时,必须显式执行以下操作:
创建、提交和结束事务。
创建并结束运行事务的会话。
实施错误处理逻辑。
借助便捷事务 API 框架,您能够执行事务,而无需负责提交或结束事务。当服务器引发某些错误类型时,该 API 会自动纳入错误处理逻辑以重新尝试操作。如需有关此行为的更多信息,请参阅本指南的“事务错误”部分。
重要
对于 MongoDB Server 4.2 及更早版本,您只能在事务中对已存在的集合执行写操作。对于 MongoDB Server 4.4 及更高版本,当您在事务中执行写操作时,服务器会根据需要自动创建集合。如需有关此行为的更多信息,请参阅服务器手册中的在事务中创建集合和索引。
Core API
Core API 提供以下方法来执行事务:
startSession(): creates a new
ClientSessioninstancestartTransaction():开始新事务
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 });
提示
显式资源管理
The Node.js driver natively supports explicit resource management for MongoClient, ClientSession, ChangeStreams, and cursors. This feature is experimental and subject to change. To learn how to use explicit resource management, see the v6.9 Release Notes.
要查看使用此 API 的完全可运行的示例,请参阅使用 Core API 用法示例。
便捷事务 API
便捷事务 API 提供以下实施事务的方法:
withSession():运行会话中传递给它的回调。API 会自动处理会话的创建和终止。
withTransaction(): Runs the callback passed to it within a transaction and calls the
commitTransaction()method when the callback returns.
这些方法返回 回调 返回的值。例如,如果您传递给 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. |
| Specifies the write operation level of acknowledgment required from a replica set. |
| Specifies how to route read operations to members of a replica set. |
| 指定对事务的提交动作可以运行的时间长度,以毫秒为单位。 |
有关选项的完整列表,请参阅 TransactionOptions 的 API 文档。
注意
事务从您的 MongoClient 实例继承设置,除非您在事务选项中指定设置。
以下代码展示了如何定义事务选项并将其传递给 startTransaction() 方法:
const txnOpts = { readPreference: 'primary', readConcern: { level: 'local' }, writeConcern: { w: 'majority' }, maxCommitTimeMS: 1000 }; session.startTransaction(txnOpts);
事务错误
由于MongoDB事务符合ACID,因此驱动程序可能会在操作过程中产生错误,以确保数据保持一致。如果出现以下错误,您的应用程序必须重试ACID 事务:
TransientTransactionError如果写入操作在驱动程序提交事务之前遇到错误,则会引发此错误。要学习有关此错误类型的更多信息,请参阅服务器手册中驱动程序API页面上的 TransientTransactionError 说明。UnknownTransactionCommitResult:如果提交操作遇到错误,则引发此错误。要学习;了解有关此错误类型的更多信息,请参阅服务器手册中驱动程序API页面上的 UnknownTransactionCommitResult 描述。
以下部分介绍了在使用不同 API 时如何处理这些错误。
便捷事务 API错误处理
便捷事务 API 包含了针对这些错误类型的重试逻辑。驱动程序会自动重试事务,直到成功提交。
核心API错误处理
如果使用 Core API执行ACID 事务,则必须在应用程序中添加以下错误处理函数:
一个函数,当驱动程序遇到以下情况时,该函数会重试整个事务:
TransientTransactionError一个函数,当驱动程序遇到以下情况时,该函数会重试提交操作:
UnknownTransactionCommitResult
这些函数必须运行,直到成功提交或出现其他错误。有关此重试逻辑的示例,请参阅服务器手册中驱动程序API页面上的核心API部分。