Docs 菜单
Docs 主页
/ / /
Ruby MongoDB 驱动程序
/

事务

在此页面上

  • 使用事务
  • 低级 API
  • 重试提交
  • 事务嵌套

MongoDB 服务器 4.0版本引入了多文档事务。 (在所有版本的MongoDB中,对单个文档中多个字段的更新都是原子性的。) Ruby驾驶员版本2.6.0 添加了对事务的支持。

要启动ACID 事务,应用程序必须具有会话。

使用事务的推荐方法是利用 with_transaction辅助方法:

session = client.start_session
session.with_transaction do
collection.insert_one({hello: 'world'}, session: session)
end

with_transaction助手会执行以下操作:

  • 它在调用提供的区块之前启动事务,并在区块完成时提交事务。

  • 如果区块中的任何操作或提交操作导致暂时性事务错误,则将再次执行区块和/或提交操作。

该区块应该是幂等的,因为它可能会被多次调用。

该区块可以通过调用commit_transactionabort_transaction显式提交或中止事务;在这种情况下, with_transaction不会尝试提交或中止(但仍可能在出现暂时性事务错误传播出区块时重试区块)。

如果事务的提交结果未知,也会重试该区块。 例如,如果集群在提交期间进行选举,则可能会发生这种情况。 在这种情况下,当重试区块时,拓扑结构的主服务器可能会发生变化。

目前,自开始执行起经过 120 秒后, with_transaction将停止重试区块和提交。 该时间不可配置,并且可能在未来的驱动程序版本中发生变化。 请注意,这并不能保证with_transactions的总运行时间为 120 秒或更短,只是保证一旦 120 秒的挂钟时间过去,就不会再启动重试尝试。

如果需要对事务进行更多控制,也可以使用低级 API。

with_transaction 采用与start_transaction相同的选项,分别是读关注(read concern)、写关注(write concern)和读取偏好(read preference):

session = client.start_session
session.with_transaction(
read_concern: {level: :majority},
write_concern: {w: 3},
read: {mode: :primary}
) do
collection.insert_one({hello: 'world'}, session: session)
end

如果with_transaction区块内的命令失败,可能会导致服务器上的事务中止。 这种情况通常由驱动程序以透明方式处理。 但是,如果应用程序捕获此类错误且未重新引发该错误,则驱动程序将无法确定事务是否已中止。 然后,驱动程序将无限期地重试该区块。

为了避免这种情况,应用程序不得以静默方式处理with_transaction区块中的错误。 如果应用程序需要处理区块内的错误,则必须重新引发错误。

session.with_transaction do
collection.insert_one({hello: 'world'}, session: session)
rescue Mongo::Error::OperationFailure => e
# Do something in response to the error
raise e
end

如果应用程序需要以自定义方式处理错误,则应改用低级API 。

可以通过在会话上调用start_transaction方法来启动事务:

session = client.start_session
session.start_transaction

还可以在启动事务(transaction)时指定读关注(read concern)、写关注(write concern)和读取偏好(read preference):

session = client.start_session
session.start_transaction(
read_concern: {level: :majority},
write_concern: {w: 3},
read: {mode: :primary})

要将事务中的更改持久保存到数据库,必须显式提交事务。 如果会话以打开事务结束,则该事务将中止。 事务也可以显式中止。

要提交或中止事务,请在会话实例上调用commit_transactionabort_transaction

session.commit_transaction
session.abort_transaction

注意:未完成的事务可以锁定服务器中的各种对象,例如数据库。 例如,以下代码片段中的删除调用将挂起transactionLifetimeLimitSeconds秒(默认为60 ),直到服务器过期并中止事务:

c1 = Mongo::Client.new(['127.0.0.1:27017']).use(:test_db)
session = c1.start_session
c1['foo'].insert_one(test: 1)
session.start_transaction
c1['foo'].insert_one({test: 2}, session: session)
c2 = Mongo::Client.new(['127.0.0.1:27017']).use(:test_db)
# hangs
c2.database.drop

由于事务与服务器端会话关联,因此关闭客户端不会中止此客户端启动的事务 — 应用程序必须调用abort_transaction或等待服务器端事务超时。 除了提交或中止事务之外,应用程序还可以结束会话,这将中止此会话上正在进行的事务:

session.end_session
c2 = Mongo::Client.new(['127.0.0.1:27017']).use(:test_db)
# ok
c2.database.drop

如果事务中的命令失败,则服务器上的事务可能会中止。 中止事务的错误的错误标签中没有TransientTransactionError 。 提交此类事务的尝试将被拒绝,并显示NoSuchTransaction错误。

如果事务提交失败,可以重试。 以下是执行此操作的 Ruby 代码:

begin
session.commit_transaction
rescue Mongo::Error => e
if e.label?('UnknownTransactionCommitResult')
retry
else
raise
end
end

MongoDB 不支持嵌套事务。 在事务已在进行中时尝试调用start_transactionwith_transaction将导致错误。

后退

会话