【问题标题】:Rails 3 ActiveRecord Nested Transactions With Multiple DatabasesRails 3 ActiveRecord 具有多个数据库的嵌套事务
【发布时间】:2020-02-05 04:56:00
【问题描述】:

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

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

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

谢谢

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3 transactions


    【解决方案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
    

    【讨论】:

      【解决方案2】:

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

      为了取消跨两个数据库的事务,您需要检测嵌套事务何时将在没有外部事务的情况下回滚。标准异常将未被捕获并导致两个事务回滚。然而,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
      

      【讨论】:

        猜你喜欢
        • 2023-03-06
        • 1970-01-01
        • 2022-01-19
        • 1970-01-01
        • 2011-12-19
        • 1970-01-01
        • 2016-03-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多