【问题标题】:ActiveJob/Resque dirty reads. Transaction isolation levelActiveJob/Resque 脏读。事务隔离级别
【发布时间】:2016-08-11 23:29:05
【问题描述】:

我为同一个实体(用户)有几个 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

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-4 resque


    【解决方案1】:

    第一个选项是使用锁定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) 方法使您的代码更具可读性。

    【讨论】:

    • 锁不起作用。乐观的人行不通,因为它被设计为在单个进程中工作。悲观的人——只是没有。原始 SQL 似乎可以工作,谢谢。
    • 实际上,Rails 通过在表中添加lock_version 字段来实现乐观锁定(与DB 级别的乐观锁定略有不同)。因此,如果他们在 SQL 更新查询中实现检查锁版本,它将适用于非单进程场景​​。但是,我不确定,还没有查看源代码。您是否尝试过使用乐观锁定?如果你这样做了,但它不起作用,请告诉我。将来最好注意这一点。
    • 乐观锁默认开启。如果对象已过时,它会引发rescue ActiveRecord::StaleObjectError,这在我的情况下是不正确的。这是来自文档:This locking mechanism will function inside a single Ruby process. To make it work across all web requests, the recommended approach is to add lock_version as a hidden field to your form.
    猜你喜欢
    • 2017-07-23
    • 2011-09-30
    • 2018-08-02
    • 2015-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多