【问题标题】:Rails/ActiveRecord Pessimistic Locking - Do I need to reload after obtaining a lock?Rails/ActiveRecord 悲观锁定 - 获得锁定后是否需要重新加载?
【发布时间】:2013-03-19 15:04:30
【问题描述】:

我在 Rails 中有一个 Transaction 模型,表示将通过信用卡收取费用的金融交易。

当一个事务被创建时,它的状态是:new。当我尝试结算费用时(将在 DelayedJob 中发生),我将状态更新为 :pending。如果状态不是:new,任何后续对charge 的调用都将被忽略。

朴素版(不关心并发):

# Trigger a card to be charged
def charge_transaction
  return unless status == :new

  self.transaction do
    self.delay.settle_credit_card
    self.update_attribute(:status, :pending)
  end
end

# Actually do the charging (in a delayed worker)
def settle_credit_card
   # ... Interact with our payment gateway
end

由于这是一个负载平衡的网络应用程序,我想确保我们考虑了并发性并且不会产生重复费用(由于并发请求)。我了解乐观锁定的好处,但在这种情况下,我不介意有这个关键区域,因为同时尝试收费(或以任何方式更新事务)应该是一个例外情况。

这里尝试使用悲观的行级锁定

并发版本(选项 1)

# Trigger a card to be charged
def charge_transaction

  # Obtain row-lock
  self.with_lock do
    self.reload # Reload once lock is obtained - necessary?

    # Check status after lock/reload
    return unless status == :new

    self.delay.settle_credit_card
    self.update_attribute(:status, :pending)
  end
end

并发版本(选项 2)

# Trigger a card to be charged
def charge_transaction

  # Begin transaction without lock
  self.transaction do
    self.reload(lock: true) # Reload and obtain lock

    # Check status after lock/reload
    return unless status == :new

    self.delay.settle_credit_card
    self.update_attribute(:status, :pending)
  end
end

这些方法中的任何一种(或两种)都有效吗?获得锁后是否需要显式重新加载(以确保事务对象是当前的)或者 Rails 会在获得锁时自动执行此操作?如果两种方法都有效,哪种方法更可取?

非常感谢!

【问题讨论】:

    标签: ruby-on-rails-3 activerecord concurrency locking delayed-job


    【解决方案1】:

    这两种方法都有效并且会起作用。但是,在版本 1 中,reload 不是必需的 - 锁定会自动重新加载记录,因此您可以将其删除。

    然后,如果您检查 source code 中的 with_locklock!,您会发现您的两个版本是 100% 等效的:

    def lock!(lock = true)
      reload(:lock => lock) if persisted?
      self
    end
    
    def with_lock(lock = true)
      transaction do
        lock!(lock)
        yield
      end
    end
    

    使用with_lock 是最简单和首选的:

    # Obtain row-lock
    with_lock do
      # Check status after lock/reload
      return unless status == :new
    
      delay.settle_credit_card
      update_attribute(:status, :pending)
    end
    

    (注意:您可以安全地从方法调用中删除self

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-12
      • 1970-01-01
      • 2014-08-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多