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

12

我在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

由于这是一个负载均衡的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

并发版本(选项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会自动执行? 如果两种方法都有效,哪种更可取?

非常感谢!

1个回答

17

这两种方法都是有效的,都能工作。在版本1中,reload 不是必要的,因为锁定会自动重新加载记录,所以你可以将其删除。

然后,如果你查看 with_locklock!源代码,你会发现这两个版本完全等效:

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接