文档菜单
文档首页
/ / /
Ruby MongoDB 驱动程序
/

事务

本页内容

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

MongoDB 服务器 4.0 版本引入了多文档事务。(在单个文档内更新多个字段在所有 MongoDB 版本中都是原子的。)Ruby 驱动程序版本 2.6.0 添加了对事务的支持。

为了开始一个事务,应用程序必须有一个会话.

使用事务的推荐方法是利用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 不会尝试提交或中止(但仍然可能在块外传播的临时事务错误上重试块)。

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

目前,with_transaction 会在其执行开始后的120秒内停止重试块和提交。此时间不可配置,并且可能在未来的驱动程序版本中更改。请注意,这并不保证 with_transactions 的总体运行时间将是120秒或更少——只是当壁钟时间过去120秒后,不会发起进一步的尝试。

如果需要更多对事务的控制,也提供了一组低级API。

with_transaction 使用与 start_transaction 相同的选项,包括读取关注点、写入关注点和读取偏好

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

在开始事务时,也可以指定读取关注点、写入关注点和读取偏好

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

注意:未解决的事务可能持有对服务器上各种对象的锁定,例如数据库。例如,以下片段中的drop调用将在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将会导致错误。

返回

会话