为什么Rails忽略(伪)嵌套事务中的回滚?

Mik*_*rin 17 ruby mysql activerecord transactions ruby-on-rails

根据文档ActiveRecord :: Transactions :: ClassMethods,非新嵌套事务将忽略回滚.来自文档:

User.transaction do
  User.create(username: 'Kotori')
  User.transaction do
    User.create(username: 'Nemu')
    raise ActiveRecord::Rollback
  end
end
Run Code Online (Sandbox Code Playgroud)

raise ActiveRecord::Rollback被忽略,因为它是在一个子事务(或者更确切地说,它仍然是在父事务中,而不是自己的).我不明白为什么两个都会忽略回滚调用?我可以看到,因为子'事务'不是真正的事务,它不会回滚'Nemu'块,但为什么它不会触发父项的回滚?子事务是否以某种方式隐藏了回滚?

换句话说,为什么似乎无法从嵌套子项中回滚父事务?

Nim*_*mir 15

实际上,这正是嵌套交易的设计方式.我引用oracle文档:

嵌套事务用于为在较大事务范围内执行的操作子集提供事务保证.这样做允许您独立于较大的事务提交和中止操作子集.

因此,常规嵌套事务中的子事务对于他或其他子项或父项(较大事务)的行为方式没有发言权,除了更改相互数据或未能发生异常.

但是你可以通过传递使用rails docs中所述的功能,给予他(儿童交易)一个非常有限的投票机会.sub-transactionrequires_new: true

User.transaction do
  User.create(username: 'Kotori')
  User.transaction(requires_new: true) do
    User.create(username: 'Nemu')
    raise ActiveRecord::Rollback
  end
end
Run Code Online (Sandbox Code Playgroud)

正如文档所说:只创造'Kotori'.因为强大的'Nemu'孩子选择了默默地死去.

有关嵌套事务规则的更多详细信息(oracle docs)

更新:

为了更好地理解为什么rails以nested transactions这种方式工作,你需要更多地了解嵌套事务如何在数据库级别工作,我引用rails api docs:

大多数数据库不支持真正的嵌套事务...为了解决这个问题,#transaction将使用保存点模拟嵌套事务的影响:http://dev.mysql.com/doc/refman/5.0/ EN/savepoint.html

好的,然后文档描述了nested transaction上述两种情况中a的行为如下:

在嵌套调用的情况下,#transaction将表现如下:

  • 该块将在不执行任何操作的情况下运行.块内发生的所有数据库语句都有效地附加到已打开的数据库事务中.

  • 但是,如果设置了:requires_new,则该块将被包装在充当子事务的数据库保存点中.

我想小心,只想象:

选项(1)(没有 requires_new)是为了防止您使用完全支持的DBMS,nested transactions或者您对"假"行为感到满意nested_attributes

选项(2)是支持savepoint解决方法,如果你不支持.

  • 但是为什么要求使用`require_new`,如果替代方案是Rollback确实没有* - 默认情况下. (4认同)
  • 我仍然不明白为什么`requires_new:true`的行为不是嵌套事务的默认行为.为什么我不想使用它? (2认同)

Ben*_*don 9

这是因为transaction do块如何专门处理ActiveRecord::Rollback在这些块中引发的异常以及 Railstransaction do默认如何将嵌套块连接在一起的交互。

  1. transaction do根据其中引发的异常类型,Rails块的行为略有不同:

    • 当块ActiveRecord::Rollback内引发异常时transaction do,这些异常会被transaction do块拯救,并且不会进一步冒泡。
    • 所有其它各种异常情况的抢救和重新提出transaction do继续泡了超越该块。
  2. 默认情况下,Rails 将嵌套事务“连接”在一起。这意味着只有当最外部的事务有异常冒泡时,事务才会被中止。

总之,这两种行为意味着,当ActiveRecord::Rollback在嵌套事务中提出的,它是由内部救出transaction do块确实不能再提升; 外部transaction do块,因为它没有收到异常,成功完成。

要强调,如果你提出任何异常其他ActiveRecord::Rollback,它会继续向上冒泡通过多个transaction do模块和外部交易将中止预期。

正如在别处提到的,你可以强制 Rails 的嵌套事务不“加入”它们的父事务transaction(requires_new: true) do;以及强制具有transaction(joinable: false) do. 它被推荐为始终同时使用transaction(joinable: false, requires_new: true) do

  • 谢谢!这是关于异常和 tx 的行为方式以及人们将使用“joinable”的最清晰和解释性的答案。 (2认同)