ActiveJob/Resque 脏读。事务隔离级别
ActiveJob/Resque dirty reads. Transaction isolation level
我有几个 Resque 工作人员为同一个实体(用户)服务。成功处理后,它应该减少 call_left
属性。
它与 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
第一个选项是使用 locking(optimistic or pessimistic). The documentation explains their differences and you can choose the one that suits your case. Also, 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)
方法使您的代码更具可读性。
我有几个 Resque 工作人员为同一个实体(用户)服务。成功处理后,它应该减少 call_left
属性。
它与 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
第一个选项是使用 locking(optimistic or pessimistic). The documentation explains their differences and you can choose the one that suits your case. Also, 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)
方法使您的代码更具可读性。