根据rails文档,涉及多个数据库的嵌套事务应该是有效的:
User.transaction do
User.create(:username => 'Kotori')
User.transaction(:requires_new => true) do
User.create(:username => 'Nemu')
raise ActiveRecord::Rollback
end
end如何传播嵌套事务中的回滚,以便父事务也能回滚?
谢谢
发布于 2012-12-19 02:16:45
首先,您的代码没有使用多个数据库。两者都使用来自客户端模型的连接。
其次,您没有指定DBMS。不同的DB驱动程序具有不同的属性,并不是所有驱动程序都实现嵌套事务。您使用的是哪种数据库?
我只是做了你想做的,并且我使用了两个postgres数据库。只抛出ActiveRecord::Rollback将退出封闭的事务,但不会抛出进一步的异常。我想确保如果嵌套事务失败,则外部事务将回滚。因此,我在内部事务中捕获任何异常,然后从那里引发回滚。
注意:帐户和用户模型使用不同的数据库,尽管它们都运行在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发布于 2020-02-05 04:31:33
Jonathan所说的是正确的,但他的代码示例并不完全正确。
为了取消两个数据库之间的事务,您需要检测嵌套事务何时将在没有外部事务的情况下回滚。标准异常将不会被捕获,并导致两个事务回滚。但是,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
# ...
# endhttps://stackoverflow.com/questions/11467121
复制相似问题