【问题标题】:Rollback entire transaction within nested transaction在嵌套事务中回滚整个事务
【发布时间】:2020-04-11 10:23:44
【问题描述】:

我希望嵌套事务使父事务失败。

假设我有以下模型

class Task < ApplicationRecord
  def change_status(status, performed_by)
    ActiveRecord::Base.transaction do
      update!(status: status)
      task_log.create!(status: status, performed_by: performed_by)
    end
  end
end

我一直希望 updatetask_log 创建是一起执行的事务,或者根本不执行。

如果我有一个控制器可以让我更新多个任务

class TaskController < ApplicationController
  def close_tasks
    tasks = Task.where(id: params[:_json])

    ActiveRecord::Base.transaction do
      tasks.find_each do |t|
        t.change_status(:close, current_user)
      end
    end
  end
end

我希望如果任何change_status 失败,整个请求都会从父级事务回滚。

但是,这不是 Rails 中的预期行为,请参阅 Nested Transactions 上的文档

他们举了两个例子。

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

这将创建Users“Kotori”和“Nemu”,因为父级永远不会看到加薪

那么下面的例子:

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

只创建“Kotori”,因为只有嵌套事务失败。

那么我怎样才能让 Rails 了解嵌套事务是否失败,从而使父事务失败。继续上面的示例,我希望它不会创建“Kotori”和“Nemu”。

【问题讨论】:

    标签: ruby-on-rails activerecord ruby-on-rails-5


    【解决方案1】:

    您可以确保交易不可加入

    User.transaction(joinable:false) do 
      User.create(username: 'Kotori')
      User.transaction(requires_new: true, joinable: false) do 
        User.create(username: 'Nemu') and raise ActiveRecord::Rollback
      end 
    end 
    

    这将导致类似于:

    SQL (12.3ms)  SAVE TRANSACTION active_record_1
    SQL (11.7ms)  SAVE TRANSACTION active_record_2
    SQL (11.1ms)  ROLLBACK TRANSACTION active_record_2
    SQL (13.6ms)  SAVE TRANSACTION active_record_2
    SQL (10.7ms)  SAVE TRANSACTION active_record_3
    SQL (11.2ms)  ROLLBACK TRANSACTION active_record_3
    SQL (11.7ms)  ROLLBACK TRANSACTION active_record_2 
    

    您当前的示例结果在哪里

    SQL (12.3ms)  SAVE TRANSACTION active_record_1
    SQL (13.9ms)  SAVE TRANSACTION active_record_2
    SQL (28.8ms)  ROLLBACK TRANSACTION active_record_2
    

    虽然requires_new: true 创建“新”事务(通常通过保存点),但回滚仅适用于该事务。当该事务回滚时,它只是丢弃事务并利用保存点。

    通过使用requires_new: true, joinable: false,rails 将为这些新事务创建保存点,以模拟真正嵌套事务的概念,当调用回滚时,它将回滚所有事务。

    你可以这样想:

    • requires_new: true 阻止此交易加入其父交易
    • joinable: false 表示父事务不能被子事务加入

    同时使用两者时,您可以确保任何事务都不会被丢弃,并且任何地方的 ROLLBACK 都会导致任何地方的 ROLLBACK。

    【讨论】:

    • 这非常有趣,我需要测试一下。 joinable 是否记录在任何地方?
    • 推断出比记录的https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/DatabaseStatements.html#method-i-transaction更多,如果您查看源代码,您可以看到如果当前事务不是joinable,则该语句将在新事务中执行。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多