【发布时间】:2015-09-04 13:29:31
【问题描述】:
我在使用 PostgreSQL 的 ActiveRecord 中遇到了争用情况,我正在读取一个值,然后将其递增并插入一条新记录:
num = Foo.where(bar_id: 42).maximum(:number)
Foo.create!({
bar_id: 42,
number: num + 1
})
在规模上,多个线程将同时读取然后写入 number 的相同值。将其包装在事务中并不能修复竞争条件,因为 SELECT 不会锁定表。我不能使用自动增量,因为number 不是唯一的,它只有在给定某个bar_id 时才是唯一的。我看到了 3 个可能的修复:
-
显式使用 postgres 锁(行级锁?)
-
使用唯一约束并在失败时重试(糟糕!)
-
覆盖保存以使用子选择,即 I.E.
INSERT INTO foo (bar_id, number) VALUES (42, (SELECT MAX(number) + 1 FROM foo WHERE bar_id = 42));
所有这些解决方案似乎我要重新实现ActiveRecord::Base#save! 的大部分内容有没有更简单的方法?
更新:
我以为我用Foo.lock(true).where(bar_id: 42).maximum(:number) 找到了答案,但它使用了SELECT FOR UDPATE,这在聚合查询中是不允许的
更新 2:
我们的 DBA 刚刚通知我,即使我们可以执行 INSERT INTO foo (bar_id, number) VALUES (42, (SELECT MAX(number) + 1 FROM foo WHERE bar_id = 42)); 也无法解决任何问题,因为 SELECT 在与 INSERT 不同的锁中运行
【问题讨论】:
-
如果它是动态计算的,它会随着时间的推移而改变,并且受制于相同的竞争条件
标签: ruby-on-rails postgresql activerecord