10

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

创建事务时,其状态为:new。当我尝试结算费用时(将在 DelayedJob 中发生),我将状态更新为:pendingcharge如果 status 不是,则任何后续调用都将被忽略: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

由于这是一个 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

并发版本(选项 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 会在获得锁时自动执行此操作?如果两种方法都有效,那么哪种方法更可取?

非常感谢!

4

1 回答 1

16

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

然后,如果您检查 and 的源代码,您会发现您的两个版本是 100% 等效的: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方法调用)

于 2014-02-13T12:57:01.793 回答