事务
概述
在本指南中,您可以学习如何使用Node.js驱动程序执行交易。交易允许您运行一系列操作,直到整个交易提交之前不会更改任何数据。如果交易中的任何操作失败,驱动程序将结束交易并丢弃所有数据更改,这些更改永远不会变得可见。此功能称为原子性。
由于MongoDB中单个文档上的所有写操作都是原子的,您可能想使用交易来执行修改多个文档的原子更改。这种情况需要多文档交易。多文档交易是ACID兼容的,因为MongoDB保证您交易操作涉及的数据保持一致性,即使驱动程序遇到意外的错误。
有关ACID兼容性和交易的更多信息,请参阅我们关于ACID交易的文章.
注意
要执行多文档交易,您必须连接到运行MongoDB服务器版本4.0或更高版本的部署。
有关限制的详细列表,请参阅服务器手册中的交易和操作部分。
在MongoDB中,多文档交易在客户端会话中运行。客户端会话是您希望顺序执行的关联读取或写入操作的分组。我们建议您重用客户端进行多个会话和交易,而不是每次都实例化一个新的客户端。
当与majority
读取和写入关注点结合使用时,驱动程序保证了操作之间的因果关系一致性。有关更多信息,请参阅服务器手册中的客户端会话和因果关系保证部分。
有关如何使用驱动程序执行多文档交易的更多信息,请参阅本指南的以下部分
交易API
该驱动程序提供了两个用于执行事务的API,即核心API和便捷事务API。
核心API是一个框架,允许您创建、提交和结束事务。在使用此API时,您必须显式执行以下操作
创建、提交和结束事务。
创建和结束执行事务的会话。
实现错误处理逻辑。
便捷事务API是一个框架,允许您在不负责提交或结束事务的情况下执行事务。此API自动集成错误处理逻辑,在服务器抛出某些错误类型时自动重试操作。有关此行为的更多信息,请参阅本指南的事务错误部分。
重要
当您连接到MongoDB服务器版本4.2或更早版本时,您只能在已存在的集合上执行事务写入操作。当您连接到MongoDB服务器版本4.4及更高版本时,服务器在您执行事务写入操作时自动根据需要创建集合。有关此行为的更多信息,请参阅服务器手册中的在事务中创建集合和索引部分。
核心API提供了以下方法来实现事务
startSession():创建一个新的
ClientSession
实例startTransaction():开始一个新的事务
commitTransaction():提交在会话中创建的活动事务
abortTransaction():结束在会话中创建的活动事务
endSession(): 结束当前会话
在使用此API时,您必须执行以下步骤
将会话实例传递给要在该会话中运行的每个操作。
实现一个
catch
块,用于识别服务器事务错误并实现错误处理逻辑。
以下代码演示了如何使用核心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的完整可运行示例,请参阅使用核心API使用示例。
便捷事务API
便捷事务API提供了以下方法来实现事务
withSession():在会话中运行传递给它的回调函数。API自动处理会话的创建和终止。
withTransaction():在事务中运行传递给它的回调函数,并在回调函数返回时调用
commitTransaction()
方法。
这些方法返回回调函数返回的值。例如,如果您将文档{ hello: "world" }
传递给withTransaction()
方法,那么withTransaction()
方法也返回该文档。
重要
为了避免无限循环错误,请确保传递给withTransaction()
方法的回调函数捕获它引发的任何错误。
当您使用方便的事务API时,可以将回调函数的返回值传播为withTransaction()
和withSession()
方法的返回值,以便在代码的其他部分中使用它们。
在使用此API时,您必须执行以下步骤
将会话实例传递给要在该会话中运行的每个操作。
在会话中的每个操作中都实现异步
await
语法。避免并行执行,例如调用
Promise.all()
方法。通常,并行使用会话会导致服务器错误。
以下代码演示了如何使用方便的事务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使用示例。
事务选项
您可以将一个 TransactionOptions
实例传递给 startTransaction()
和 withTransaction()
方法来配置驱动程序执行事务的方式。当您指定一个选项时,它将覆盖您可能在 MongoClient
实例上设置的选项值。
下表包含您可以在 TransactionOptions
实例中指定的选项
设置 | 描述 |
---|---|
readConcern | 指定副本集的读取操作一致性。 有关更多信息,请参阅服务器手册中的读取关注。 |
writeConcern | 指定从副本集获取写入操作确认所需的级别。 有关更多信息,请参阅服务器手册中的写入关注。 |
readPreference | 指定如何将读取操作路由到副本集的成员。 有关更多信息,请参阅服务器手册中的读取偏好。 |
maxCommitTimeMS | 指定事务提交操作可以运行的时间长度,以毫秒为单位。 |
有关选项的完整列表,请参阅TransactionOptions 的API文档。
注意
除非您在事务选项中指定它们,否则事务将从您的 MongoClient
实例继承设置。
以下代码演示了如何定义和传递事务选项给 startTransaction()
方法
const txnOpts = { readPreference: 'primary', readConcern: { level: 'local' }, writeConcern: { w: 'majority' }, maxCommitTimeMS: 1000 }; session.startTransaction(txnOpts);
事务错误
如果您正在使用 Core API 来执行事务,您必须将错误处理逻辑整合到您的应用程序中,以处理以下错误
TransientTransactionError
:如果在驱动程序提交事务之前写入操作出错,则会抛出此错误。有关此错误的更多信息,请参阅服务器手册中驱动程序 API 页面上的 TransientTransactionError 描述。UnknownTransactionCommitResult
:如果在提交操作中遇到错误,则会抛出此错误。有关此错误的更多信息,请参阅服务器手册中驱动程序 API 页面上的 UnknownTransactionCommitResult 描述。
方便的事务 API 集成了这些错误类型的重试逻辑,因此驱动程序将重试事务,直到成功提交。