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
答
第一种选择是使用锁定(optimistic或pessimistic)。文档解释了它们的差异,你可以选择适合你的案例。此外,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)
方法使代码更具可读性。
锁不起作用。乐观的人不能工作,因为它被设计为在单一过程中工作。悲观的 - 只是没有。 原始SQL似乎工作,谢谢。 –
实际上,Rails通过向表中添加'lock_version'字段来实现乐观锁定(与数据库级别乐观锁定略有不同)。因此,如果它们在SQL更新查询中检查锁定版本,它将在非单一过程方案中起作用。但是,我不确定,没有查看源代码。你尝试过使用乐观锁吗?如果你这样做,它不起作用,请让我知道。今后注意这一点很好。 – Uzbekjon
乐观锁定默认打开。如果对象已过时,它会引发'抢救ActiveRecord :: StaleObjectError',在我的情况中不是这样。 这是来自文档: '这个锁定机制将在单个Ruby进程中运行。为了使其适用于所有Web请求,推荐的方法是将lock_version作为隐藏字段添加到您的表单中 –