【问题标题】:Rails: Why does this PG transaction not work?Rails:为什么这个 PG 事务不起作用?
【发布时间】:2020-05-19 17:07:51
【问题描述】:

我过去曾在许多应用中使用过事务,它们总是按预期工作,但现在却不是。我想我只是在忽略一些明显的事情......

为什么要创建第一条记录?

ActiveRecord::Base.transaction do
  Order.create!(order_id: 12)
  raise 'breaking now'
  Order.create!(order_id: 13)
end

我正在使用Rails 6Postgres 12

编辑:现在几乎没有任何效果......我很困惑。这也不起作用(第一条记录被更新)

ActiveRecord::Base.transaction do
  order = Order.first
  order.update(order_id: 777)
  raise 'oops'
  Order.create!(order_id: 13)
end

这个Order 模型正在使用不同的数据库,但我在使用主数据库的模型上尝试了相同的操作,并且效果相同。

class Order < ApplicationRecord
  connects_to database: {reading: :orders, writing: :orders}
end

编辑:我也是从一个新的rails c 运行它,所以我的代码中没有嵌套事务,我没有在我的示例中显示。

【问题讨论】:

  • 你正在连接到不同的数据库,所以你应该说Order.transaction do ... end
  • 巧合的是,当大量 AR 被移动到单独的数据库时,我遇到了同样的事情。
  • 那么如果我需要在事务中编辑 2 个模型(在同一个数据库中)会发生什么?我曾经能够将模型与ActiveRecord::Base.transaction do 混合使用

标签: ruby-on-rails ruby postgresql


【解决方案1】:

我相信您正在寻找:

ActiveRecord::Base.connected_to(database: :replica) do
  # you can probably execute plain sql transaction command here and then commit in the end instead of below...
  Order.transaction do 
     # ... all statements that need transaction wrapping from replica
  end
end

另外,while_preventing_writes 也很方便:

ActiveRecord::Base.connected_to(role: :orders) do
  Order.connection.while_preventing_writes do
    # will raise because we're blocking writes
    Order.create!
  end
end

ActiveRecord::Base.connected_to(role: :orders) do
  Order.connection.while_preventing_writes do
    # will not raise as we're not writing
    Order.first
  end
end

语句可以来自不同的模型,但在一个块中它们应该属于同一个数据库。 原因是:“事务不分布在数据库连接之间”——这超出了 ActiveRecord 的范围(参见:https://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html

如果您确实需要跨来自不同数据库连接的模型进行事务处理,则必须使用文档中提到的解决方法:

Student.transaction do
  Course.transaction do
    course.enroll(student)
    student.units += course.units
  end
end

Rails 6.1 的一些改进似乎可以在这方面实现更简单的 api。如果您想了解更多信息 - 例如,请参阅此 comment 和引用的 PR

【讨论】:

  • 太棒了,所以我认为我仍然需要在 connected_to 中的 Base.transaction 对吧?非常感谢您的回答
  • 是的。一般来说,正如它在文档中所说:“虽然事务类方法在某些 Active Record 类上被调用,但事务块中的对象不必都是该类的实例。这是因为事务是每个数据库连接,而不是每个-模型。”因此,您可以将 Order.transaction 用于多个模型。如果这看起来很奇怪,您可以创建一个抽象模型 (MyDbBase &lt; ActiveRecord::Base),然后从中继承 A、B。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-06
相关资源
最近更新 更多