1

根据 Rails 文档,涉及多个数据库的嵌套事务应该可以工作:

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

您将如何在嵌套事务中传播回滚,以便父事务也回滚?

谢谢

4

2 回答 2

1

首先,您的代码不使用多个数据库。两者都使用来自客户端模型的连接。

其次,您没有指定 DBMS。不同的 DB 驱动程序具有不同的属性,并且并非所有驱动程序都实现嵌套事务。您使用的是哪个数据库?

我只是做了你想做的事,我正在使用两个 postgres 数据库。只是抛出 ActiveRecord::Rollback 将退出封闭事务,但不会抛出进一步的异常。我想确保嵌套事务是否失败,然后回滚外部事务。所以,我在内部事务中捕获任何异常,然后从那里引发一个回滚。

注意:Account 和 User 模型使用不同的数据库,尽管它们都运行在同一个 Postgres 实例中。此外,如果您省略了 begin rescue 子句,那么是的,您的代码将回滚两个数据库,但是在外部事务结束后,您将得到一个异常展开您的堆栈。最后,我认为如果提交外部事务有问题,那么内部事务不会回滚。就我而言,这不是问题。您可能希望将 DB1 代码包装在一个嵌套事务中,并在启动 DB2 事务之前提交它。

Account.transaction do
  begin
    acc = Account.do_something_to_accounts
    User.transaction do
      User.do_something_to_users
    end
  rescue => e
    raise ActiveRecord::Rollback
  end
end
于 2012-12-18T18:16:45.187 回答
1

乔纳森说的是正确的,但他的代码示例并不完全正确。

为了取消跨两个数据库的事务,您需要检测嵌套事务何时将在没有外部事务的情况下回滚。标准异常将未被捕获并导致两个事务回滚。然而,该ActiveRecord#transaction块将默默地从 a 中拯救和回滚ActiveRecord::Rollback,因此解决方案需要在嵌套事务的每个级别(本例中只有 1 级)中检测这种情况并传播它。

一种解决方案可能如下所示:

Account.transaction do
  acc = Account.do_something_to_accounts
  should_rollback = false
  User.transaction do
    begin
      User.do_something_to_users
    rescue ActiveRecord::Rollback
      should_rollback = true
      raise ActiveRecord::Rollback
    end
  end
  raise ActiveRecord::Rollback if should_rollback

end

另一种实现:

class NestedTransactionRollback < RuntimeError; end

begin
  Account.transaction do
    acc = Account.do_something_to_accounts
    User.transaction do
      begin
        User.do_something_to_users
      rescue ActiveRecord::Rollback
        raise NestedTransactionRollback
      end
    end
  end
rescue NestedTransactionRollback
  # Rescue silently...
end

编辑:您还可以花点心思构建一个通用解决方案,如下所示:


class DistributedDbRollback < RuntimeError; end

def cross_db_transaction(*ar_classes, nested: false, &block)
  return distributed_yield(&block) if ar_classes.empty?

  ar_classes[0].transaction do
    cross_db_transaction(ar_classes[1..-1], nested: true, &block)
  end
rescue DistributedDbRollback
  raise DistributedDbRollback if nested
  # Return nil if we're in the outermost call
end

def distributed_yield(&block)
  yield
rescue ActiveRecord::Rollback
  raise DistributedDbRollback
end

# Usage:
# cross_db_transaction(User, Account, Invoice) do
#   ...
# end
于 2020-02-04T20:31:32.590 回答