繁忙应用程序中的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
但从我在网上找到的情况来看,内部事务的
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控制台并处理事务,很容易回答这些问题。
回答重大问题:
是的,我对行锁的理解是正确的。在保存点内获取的独占锁不会在释放保存点时释放,而是在提交整个事务时释放。
不,没有更改锁类型的命令。那会是什么魔法?一旦您拥有一个独占锁,所有将触及该行的查询都必须等待您释放该锁,然后才能继续。
除了提交事务外,回滚保存点或事务还将释放独占锁。
在我的应用程序中,我通过使用多个事务并在应用程序中非常小心地跟踪状态来解决我的问题。这为重构提供了一个很好的机会,代码的最终版本更简单、更清晰、更易于维护,尽管这样做的代价是要比“throw-it-all-in-a-PG-transaction”方法更分散一些。
关于ruby-on-rails - ActiveRecord和Postgres行锁定,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/35374937/