ruby-on-rails - ActiveRecord 和 Postgres 行锁定

标签 ruby-on-rails postgresql activerecord transactions

繁忙应用程序中的 API 客户端正在争夺现有资源。他们一次请求 1 或 2 个记录,然后尝试对这些记录执行操作。我正在尝试使用事务来保护状态,但无法清楚地了解行锁,尤其是嵌套事务(我猜是保存点,因为 PG 并不真正在事务内执行事务?)关心。

该过程应如下所示:

  • 请求 N 个资源
  • 从池中删除这些资源,以防止其他用户尝试占用它们
  • 利用这些资源执行操作
  • 如果发生错误,则回滚整个事务并将资源返回池

(假设所有示例均顺利。请求总是会导致产品被退回。)

一个版本可能如下所示:

def self.do_it(request_count)
  Product.transaction do
    locked_products = Product.where(state: 'available').lock('FOR UPDATE').limit(request_count).to_a
    Product.where(id: locked_products.map(&:id)).update_all(state: 'locked')
    do_something(locked_products)
  end
end

在我看来,如果两个用户请求 2 并且只有 3 可用,那么第一行可能会出现死锁。因此,为了解决这个问题,我想做......

def self.do_it(request_count)
  Product.transaction do
    locked_products = []
    request_count.times do
      Product.transaction(requires_new: true) do
        locked_product = Product.where(state: 'available').lock('FOR UPDATE').limit(1).first
        locked_product.update!(state: 'locked')
        locked_products << locked_product
      end
    end
    do_something(locked_products)
  end
end

但是根据我在网上找到的信息,内部事务的结束不会释放行锁——它们只会在最外层事务结束时释放。

最后,我考虑了这一点:

def self.do_it(request_count)
  locked_products = []
  request_count.times do
    Product.transaction do
      locked_product = Product.where(state: 'available').lock('FOR UPDATE').limit(1).first
      locked_product.update!(state: 'locked')
      locked_products << locked_product
    end
  end
  Product.transaction { do_something(locked_products) }
ensure
  evaluate_and_cleanup(locked_products)
end

这给了我两个完全独立的事务,然后是执行该操作的第三个事务,但如果 do_something 失败,我被迫进行手动检查(或者我可以救援),这会让事情变得更加困惑。如果有人从事务中调用 do_it,这也可能导致死锁,这是很有可能的。

所以我的大问题:

  • 我对行锁释放的理解是否正确?嵌套事务中的行锁只有在最外层事务关闭时才会释放吗?
  • 是否有命令可以在不关闭事务的情况下更改锁定类型?

我的小问题:

这里是否存在一些既定或完全明显的模式,可以让某人更理智地处理这个问题?

最佳答案

事实证明,通过深入 PostgreSQL 控制台并研究事务,可以很容易地回答这些问题。

回答重大问题:

是的,我对行锁的理解是正确的。在保存点内获取的独占锁不会在保存点释放时释放,而是在整个事务提交时释放。

,没有更改锁定类型的命令。那会是什么样的巫术呢?一旦您拥有独占锁,所有涉及该行的查询都必须等待您释放锁才能继续。

除了提交事务外,回滚保存点或事务也会释放排他锁。

就我的应用程序而言,我通过使用多个事务并在应用程序内非常仔细地跟踪状态来解决我的问题。这为重构提供了一个很好的机会,并且代码的最终版本更简单、更清晰、更易于维护,尽管它的代价是比“把所有东西都扔进去”更加分散。 PG-transaction”方法。

关于ruby-on-rails - ActiveRecord 和 Postgres 行锁定,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35374937/

相关文章:

ruby-on-rails - 在 before_save Hook 中更改 ActiveRecord 属性值

mysql - 在 ActiveRecord 模型的所有查询中更改 FROM

sql - 如何在postgres中设置查询时间

sql - 关于 WHERE EXISTS(...) 中子查询的问题

node.js - 你知道 Node js 中的任何像 activerecord 和建模系统这样的 Yii

mysql - Rails Mysql2::Error: 字段中缺少引号

SQL:如何对两个表进行分组?

ruby-on-rails - 如何在 Shrine 中调整原始图像本身的大小上传

ruby-on-rails - FactoryGirl after_create 方法不保存

ruby-on-rails - 我该如何正确测试?