在嵌套事务中回滚整个事务 [英] Rollback entire transaction within nested transaction

查看:78
本文介绍了在嵌套事务中回滚整个事务的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I want a nested transaction to fail the parent transaction.

假设我有以下模型

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 的创建是一起执行的事务,或者根本不执行.

I always want the update and task_log creation to be a transaction that performs together, or not at all.

假设我有一个控制器可以让我更新多个任务

And lets say if I have a controller that allows me to update multiple tasks

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 失败,整个请求都会从父级事务回滚.

I want it so that if any of the change_status fails, that the entire request gets rolled back, from the Parent level transaction.

但是,这不是 Rails 中的预期行为,请参阅 嵌套事务

However, this isn't the expected behavior in Rails, referring to the documentation on Nested Transactions

他们举了两个例子.

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

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

Which will create both Users "Kotori" and "Nemu", since the Parent never see's the raise

然后是下面的例子:

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

只创建Kotori",因为只有嵌套事务失败.

Which only creates only "Kotori", because only the nested transaction failed.

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

So how can I make Rails understand if there is a failure in a Nested Transaction, to fail the Parent Transaction. Continuing from the example above, I want it so that neither "Kotori" and "Nemu" are created.

推荐答案

您可以确保事务不可连接

You can make sure the transactions are not joinable

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 创建新"事务(通常通过保存点),但回滚仅适用于该事务.当该事务回滚时,它只会丢弃该事务并使用保存点.

While requires_new: true creates a "new" transaction (generally via a save point) the rollback only applies to that transaction. When that transaction rolls back it simply discards the transaction and utilizes the save point.

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

By using requires_new: true, joinable: false rails will create save points for these new transactions to emulate the concept of a true nested transaction and when the roll back is called it will rollback all the transactions.

你可以这样想:

  • requires_new: true 阻止此事务加入其父事务
  • joinable: false 表示父交易不能被其子交易加入
  • requires_new: true keeps this transaction from joining its parent
  • joinable: false means the parent transaction cannot be joined by its children

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

When using both you can ensure that any transaction is never discarded and that ROLLBACK anywhere will result in ROLLBACK everywhere.

这篇关于在嵌套事务中回滚整个事务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆