繁忙应用程序中的 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/