Rails/ActiveRecord悲观锁定 - 获取锁后我需要重新加载吗?

jsh*_*kol 9 concurrency activerecord locking delayed-job ruby-on-rails-3

我在Rails中有一个交易模型,代表一个金融交易,将从信用卡中扣除.

创建事务时,它的状态为:new.当我尝试解决费用(将在DelayedJob中发生)时,我将状态更新为:pending.charge如果状态不是,则将忽略任何后续调用:new.

天真版本(不关心并发):

# 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
Run Code Online (Sandbox Code Playgroud)

由于这是一个load_balanced Web应用程序,我想确保我们考虑并发性而不是创建重复费用(由于并发请求).我理解乐观锁定的好处,但在这种情况下,我不介意拥有这个关键区域,因为同时尝试收费(或以任何方式更新交易)应该是一个例外情况.

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

并发版本(选项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
Run Code Online (Sandbox Code Playgroud)

并发版本(选项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
Run Code Online (Sandbox Code Playgroud)

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

非常感谢!

Kom*_*owy 13

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

然后,如果你检查源代码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
Run Code Online (Sandbox Code Playgroud)

使用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
Run Code Online (Sandbox Code Playgroud)

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