我正试图悲观地锁定表的子集(Postgres)以进行条件插入,并且花了很长时间找到有效的语法。这基本上是我想要做的:
ActiveRecord::Base.transaction do
if consumer.purchases.lock.sum(&:amount) < some_threshold
consumer.purchases.create!(amount: amount)
end
end
不幸的是,上述方法不起作用。但感觉应该。我只需要锁定特定消费者的所有行而不锁定整个表。不幸的是,我处理的是真钱,它是一种分类账,所以它必须是防弹的。
consumer.purchases.lock.to_sql结果SELECT "purchases".* FROM "purchases" WHERE "purchases"."consumer_id" = ? FOR UPDATE如我所料,但由于某种原因,链接.create!导致查询生成器删除FOR UPDATE锁。
好的,所以我把它拆开并尝试了各种我认为应该有效但不要的东西:
# Process 1
ActiveRecord::Base.transaction do
consumer.purchases.tap{ |p| p.lock! }.create!(amount: amount)
sleep 20
end
# Process 2
ActiveRecord::Base.transaction do
consumer.purchases.tap{ |p| p.lock! }.create!(amount: amount)
# Should wait but doesn't
end
ActiveRecord::Base.transaction do
purchases = Consumer.find(3).purchases
purchases.lock
purchase = purchases.new(amount: amount)
purchase.save!
sleep 20
end
... Other process doesn't wait...
我可以让它工作的唯一方法是迭代地锁定行;这确实有效:
# DOES WORK!
# Process 1
ActiveRecord::Base.transaction do
purchases = Consumer.where(id: 3).first.purchases
purchases.each(&:lock!)
purchase = purchases.new(amount: amount)
purchase.save!
sleep 20
end
# Process 2
ActiveRecord::Base.transaction do
purchases = Consumer.where(id: 3).first.purchases
purchases.each(&:lock!)
purchase = purchases.new(amount: amount)
purchase.save!
# waits as it should
end
但是我不能被要求迭代地锁定它们,这太疯狂了:) 所以我想也许这是一个奇怪的 Postgres 怪癖?(我对 MySQL 更熟悉),所以我在 Postgres 中手工完成,它没有问题:
BEGIN;
SELECT * FROM purchases WHERE consumer_id = 3 FOR UPDATE;
SELECT pg_sleep(30);
INSERT INTO purchases (name, amount) VALUES ('shouldBlock30Seconds', '1000');
END;
BEGIN;
SELECT * FROM purchases WHERE consumer_id = 3 FOR UPDATE;
INSERT INTO purchases (name, amount) VALUES ('shouldWait30Seconds', '1000');
END;
BEGIN;
SELECT * FROM purchases WHERE consumer_id = 24839992 FOR UPDATE;
INSERT INTO purchases (name, amount) VALUES ('shouldInsertImmediately', '1000');
END;
shouldInsertImmediately立即shouldBlock30Seconds插入,30 秒后插入,然后shouldWait30Seconds立即插入。
我正在拔头发:) 以前有没有人遇到过这个问题,或者我只是很累并且遗漏了一些明显的东西?
(Rails 5.1.7、Ruby 2.4.1、Postgres 11.6)