我正在为你可以租的东西开发一个预订系统。我希望限制多个用户保留相同的项目。
我显示一个列表,用户可以单击该列表来检查详细信息。如果任何用户已经打开了详细视图,那么其他用户就不能同时打开它。我正在维护一个标志调用is_lock
,以检查记录是否已被锁定,但当多个用户同时单击同一项时,我正面临问题。
所以我采用了悲观锁,这降低了这个问题的发生率,但是多个用户打开同一个项目,但它没有完全解决这个问题。我仍然面临着同样的问题。
begin
Item.transaction do
item = Item.lock.where(id: item_id, is_lock: false)
item.is_lock = true;
item.save!
end
rescue Exception => e
# Something went wrong.
end
上面是我实现的代码。
如果我做错了什么,请告诉我。
编辑:
我尝试了@rmlockerd提供的解决方案,方法如下:
但是上面的测试失败了,因为我能够从两个控制台获取相同的记录,即使记录是从控制台1锁定的。
在两个不同的控制台中运行rails。
发布于 2020-06-24 06:08:53
仅仅看一下您提供的代码片段可能会产生误导,但是由于您的.where
谓词,似乎存在可能的争用条件。
如果User2试图在User1之后但在事务提交之前获得同一项的锁,则.where
仍将返回带有is_lock
false的原始记录。.lock
的默认行为是简单地等待锁的出现。因此,User2将阻塞直到原始事务提交,然后获得一个锁并继续将is_lock
设置为true。
好消息是,当您获得一个锁,Rails重新加载记录,以便您得到最新的数据。在获得锁后检查is_lock
应消除该争用条件,如下所示:
Item.transaction do
item = Item.lock.find_by(id: item_id, is_lock: false) # only 1, so where is unnecessary
return if item.blank? || !item.is_lock
item.update!(is_lock: true)
end
# I have the lock stuff...
.lock
方法还接受一个可选的'locking子句‘(根据您使用的数据库而有所不同),它可以用于配置锁定行为。例如,如果您使用Postgres,您可以:
Item.transaction do
item = Item.lock('FOR UPDATE SKIP LOCKED').find_by(id: item_id, is_lock: false)
return if item.blank?
item.update!(is_lock: true)
end
SKIP LOCKED
子句指示Postgres自动跳过任何已经锁定的记录。在上述竞赛条件下,第二次呼叫.lock
将立即保释并返回零,因此只需检查物品是否存在就足够了。如果您对数据库特定的锁定子句感兴趣,请查看波斯特格斯或MySQL文档。
https://stackoverflow.com/questions/62546667
复制相似问题