9

我在 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 对你没有帮助”的人表示抱歉我的示例最初没有栏查找。现在已解决。

4

4 回答 4

7

你对做什么感到困惑with_lock。来自精美手册

with_lock(lock = true)

将传递的块包装在事务中,在屈服之前锁定对象。您可以将 SQL 锁定子句作为参数传递(请参阅 参考资料lock!)。

如果你检查with_lock内部做了什么,你会发现它只不过是一个薄包装lock!

锁定!(锁定=真)

获取此记录的行锁。重新加载记录以获取请求的锁。

所以with_lock只是做一个行锁定和锁定foo的行。

不要为所有这些锁定废话而烦恼。处理这种情况的唯一明智的方法是在数据库中使用唯一约束,除了数据库之外没有人可以确保唯一性,除非您想做一些荒谬的事情,例如锁定整个表;然后继续盲目地尝试您的 INSERT 或 UPDATE 并捕获并忽略违反唯一约束时将引发的异常。

于 2012-07-03T17:28:55.123 回答
3

处理这种情况的正确方法实际上在 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”不是原子的,它实际上是一个查找然后是一个创建。所以用你的查找替换它然后创建。这个页面上的文档准确地描述了这种情况。)

于 2016-07-27T20:14:26.777 回答
2

为什么不使用唯一约束?它是为独特而生的

于 2012-07-03T14:48:07.430 回答
1

一个锁在查询缓存中的 Rails 应用程序中不起作用的原因。

如果您尝试在单个请求中多次获取同一行的排他锁,则查询缓存会启动,因此后续锁定查询永远不会到达数据库本身。

该问题已在 Github 上报告。

于 2012-07-05T09:23:31.413 回答