ActiveJob/Resque脏读。事务隔离级别

问题描述:

我有几个同一个实体(用户)的Resque worker。成功处理后,应该减少call_left属性。ActiveJob/Resque脏读。事务隔离级别

它与perform_now(因此)完美地工作,但与perform_later(并行)产生不可预知的结果。在日志中有相同数量的提交calls_left

我试图用reload方法甚至设置最高的隔离级别。但仍然有这个问题。

如何解决?

class DataProcessJob < ActiveJob::Base 
    queue_as :default  
    def perform(user_id, profile_id) 
    User.transaction(isolation: :serializable) do 
     user = User.find(user_id).reload 
     user.data_process(profile_id) 
     user.update(calls_left: user.calls_left-1) 
    end 
    end 
end 

第一种选择是使用锁定optimisticpessimistic)。文档解释了它们的差异,你可以选择适合你的案例。此外,here is a relevant code snippet来自文档,如果你乐观锁定,可能会帮助你。

def with_optimistic_retry 
    begin 
    yield 
    rescue ActiveRecord::StaleObjectError 
    begin 
     # Reload lock_version in particular. 
     reload 
    rescue ActiveRecord::RecordNotFound 
     # If the record is gone there is nothing to do. 
    else 
     retry 
    end 
    end 
end 

第二种选择是使用原始SQL字符串查询来增加calls_left字段。底层数据库将处理原子更新。


最后但并非最不重要的是,您可以使用decrement!(:calls_left)方法使代码更具可读性。

+0

锁不起作用。乐观的人不能工作,因为它被设计为在单一过程中工作。悲观的 - 只是没有。 原始SQL似乎工作,谢谢。 –

+0

实际上,Rails通过向表中添加'lock_version'字段来实现乐观锁定(与数据库级别乐观锁定略有不同)。因此,如果它们在SQL更新查询中检查锁定版本,它将在非单一过程方案中起作用。但是,我不确定,没有查看源代码。你尝试过使用乐观锁吗?如果你这样做,它不起作用,请让我知道。今后注意这一点很好。 – Uzbekjon

+0

乐观锁定默认打开。如果对象已过时,它会引发'抢救ActiveRecord :: StaleObjectError',在我的情况中不是这样。 这是来自文档: '这个锁定机制将在单个Ruby进程中运行。为了使其适用于所有Web请求,推荐的方法是将lock_version作为隐藏字段添加到您的表单中 –