根据 Rails 文档,涉及多个数据库的嵌套事务应该可以工作:
User.transaction do
User.create(:username => 'Kotori')
User.transaction(:requires_new => true) do
User.create(:username => 'Nemu')
raise ActiveRecord::Rollback
end
end
您将如何在嵌套事务中传播回滚,以便父事务也回滚?
谢谢
根据 Rails 文档,涉及多个数据库的嵌套事务应该可以工作:
User.transaction do
User.create(:username => 'Kotori')
User.transaction(:requires_new => true) do
User.create(:username => 'Nemu')
raise ActiveRecord::Rollback
end
end
您将如何在嵌套事务中传播回滚,以便父事务也回滚?
谢谢
首先,您的代码不使用多个数据库。两者都使用来自客户端模型的连接。
其次,您没有指定 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
乔纳森说的是正确的,但他的代码示例并不完全正确。
为了取消跨两个数据库的事务,您需要检测嵌套事务何时将在没有外部事务的情况下回滚。标准异常将未被捕获并导致两个事务回滚。然而,该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