我们有一个elements
表,可以issued
给客户。这些elements
只能给客户一次,而且我们有许多客户可以同时提取所有元素的情况。然后我们需要返回与之关联的数据(因此有更新,然后选择)。
目前的解决方案是找到/更新随机版本为issued=true
,将其id
设置为LAST_INSERTED_ID
;然后立即进行选择调用以查找每个连接唯一的where('id = LAST_INSERTED_ID()')
。
由于我们正在更新issued=false
到issued=true and [last inserted]
的位置,因此一次调用足够小,不会遇到竞争条件问题。
但是,所有这些都是在SQL中完成的,并且感觉非常hackish。这似乎不是一个罕见的问题,它没有使用更多的Railsy解决方案解决。包装事务可能有助于防止出现双重问题,但是在事务失败的情况下我们需要重试逻辑。
我们没有考虑什么解决方案?
答案 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