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

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

User.transaction do User.create(username: 'Kotori') User.transaction do User.create(username: 'Nemu') raise ActiveRecord::Rollback end end 

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

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

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

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

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

但是你可以通过传递requires_new: true来利用rails docs中所述的sub-transactionfunction,授予他( 子事务 )对其命运的非常有限的投票机会requires_new: true

 User.transaction do User.create(username: 'Kotori') User.transaction(requires_new: true) do User.create(username: 'Nemu') raise ActiveRecord::Rollback end end 

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

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

更新:

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

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

好的,然后文档描述了两个提到的案例中nested transaction的行为,如下所示:

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

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

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

我想小心,只想象

如果您使用完全支持nested transactions的DBMS,或者您对nested_attributes的“假”行为感到满意,那么选项(1)没有 requires_new)就是存在的

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

这是因为transaction do交互块如何专门处理在这些块中引发的ActiveRecord::Rollbackexception以及Rails如何将默认情况下的嵌套transaction do块连接在一起。

  1. Rails transaction do块的行为略有不同,具体取决于它们中引发的exception类型:

    • transaction do块中引发ActiveRecord::Rollbackexception时, transaction do块会挽救这些exception,并且不会进一步冒泡。
    • 所有其他类型的exception都由transaction do阻止重新获得并重新引发 ,并且继续在该块之外冒泡。
  2. 默认情况下,Rails将嵌套事务“连接”在一起。 这意味着只有当最外部的交易有exception泡沫时才会中止交易。

总之,这两种行为意味着当在嵌套事务中引发ActiveRecord::Rollback时,它会被内部transaction do块挽救并且不再重新引发; 外部transaction do阻塞,因为它没有收到exception,成功完成。

要强调的是,如果你引发除ActiveRecord::Rollback之外的任何exception,它将继续通过多个transaction do块冒泡,外部事务将按预期中止。

正如其他地方所提到的,你可以强制Rails的嵌套事务不要用transaction(requires_new: true) do “连接”他们的父transaction(requires_new: true) do ; 以及强制父事务不被具有transaction(joinable: false) do的子transaction(joinable: false) do 。 建议始终使用两个transaction(joinable: false, requires_new: true) do