乔纳森说的是正确的,但他的代码示例并不完全正确。
为了取消跨两个数据库的事务,您需要检测嵌套事务何时将在没有外部事务的情况下回滚。标准异常将未被捕获并导致两个事务回滚。然而,ActiveRecord#transaction 块会默默地从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