我在rails模型中有以下代码:
foo = Food.find(...)
foo.with_lock do
if bar = foo.bars.find_by_stuff(stuff)
# do something with bar
else
bar = foo.bars.create!
# do something with bar
end
end
目标是确保创建的类型的Bar不会被创建两次。
在控制台上测试with_lock确认了我的期望。但是,在生产中,似乎在某些或所有情况下锁都没有按预期工作,并且正在尝试冗余Bar - 因此,with_lock不会(总是?)导致代码等待轮到
这里可能会发生什么?
更新 很抱歉所有说“锁定foo不会帮助你”的人!我的例子最初没有查找条形码。现在已经修好了。
答案 0 :(得分:6)
你对with_lock
的作用感到困惑。来自fine manual:
with_lock(lock = true)
包装事务中传递的块,在屈服之前锁定对象。您可以将SQL锁定子句作为参数传递(请参阅
lock!
)。
如果您检查with_lock
内部的内容,您会发现它只是lock!
周围的薄包装:
锁定!(锁定= true)
获取此记录的行锁定。重新加载记录以获取请求的锁定。
所以with_lock
只是在进行行锁并锁定foo
的行。
不要打扰所有这些锁定废话。处理这种情况的唯一理智方法是在数据库中使用唯一约束,除了数据库之外没有人可以确保唯一性,除非你想做一些荒谬的事情,比如锁定整个表;然后继续并盲目地尝试INSERT或UPDATE并陷阱并忽略违反唯一约束时将引发的异常。
答案 1 :(得分:2)
为什么不使用唯一约束?它是为了独特而制作的
答案 2 :(得分:2)
处理这种情况的正确方法实际上在Rails文档中是正确的:
http://apidock.com/rails/v4.0.2/ActiveRecord/Relation/find_or_create_by
begin
CreditAccount.find_or_create_by(user_id: user.id)
rescue ActiveRecord::RecordNotUnique
retry
end
(" find_or_create_by"不是原子的,它实际上是一个查找,然后是一个创建。所以用你的find替换它然后创建。这个页面上的文档完全描述了这个案例。)
答案 3 :(得分:1)
锁定在查询缓存中的Rails应用程序中无效的原因。
如果您尝试在单个请求中多次在同一行上获取独占锁,则查询缓存会启动,因此后续锁定查询永远不会到达数据库本身。
Github上的