在处理竞争条件时选择并更新一行?

时间:2015-10-27 19:57:13

标签: mysql ruby-on-rails activerecord

我们有一个elements表,可以issued给客户。这些elements只能给客户一次,而且我们有许多客户可以同时提取所有元素的情况。然后我们需要返回与之关联的数据(因此有更新,然后选择)。

目前的解决方案是找到/更新随机版本为issued=true 将其id设置为LAST_INSERTED_ID;然后立即进行选择调用以查找每个连接唯一的where('id = LAST_INSERTED_ID()')

由于我们正在更新issued=falseissued=true and [last inserted]的位置,因此一次调用足够小,不会遇到竞争条件问题。

但是,所有这些都是在SQL中完成的,并且感觉非常hackish。这似乎不是一个罕见的问题,它没有使用更多的Railsy解决方案解决。包装事务可能有助于防止出现双重问题,但是在事务失败的情况下我们需要重试逻辑。

我们没有考虑什么解决方案?

1 个答案:

答案 0 :(得分:0)

您将需要使用数据库级锁定来避免竞争条件。

在MySQL中执行此操作的一种方法是SELECT FOR UPDATE,如下所示:

SELECT * FROM elements WHERE issued=false LIMIT 1 FOR UPDATE

在ActiveRecord(Rails)中,这称为pessimistic locking,实现如下所示:

Element.transaction do
  element = Element.lock(true).where(issued: false).first
  element.issued = true
  # ... do other stuff to assign to a given client
  element.save!
end

如果同时启动了多次,第二次通话将被阻止,直到第一次通话结束,所以当它执行时,第一条记录已经更新为issued = true和第二次通话将返回下一条记录而不是相同的记录。

您可以阅读SELECT FOR UPDATE here