Techioz Blog

Rails ドキュメントの悲観的ロックの例での競合状態

概要

Rails ドキュメントの次の例を見ています。

account = Account.first
account.with_lock do
  # This block is called within a transaction,
  # account is already locked.
  account.balance -= 100
  account.save!
end

私の理解では、最初のメソッドはリレーション (検索と同様) ではなく、データベースから直接レコードを返します。ここで、balance = 200 で、2 つの同時リクエストがあり、両方とも with_lock ブロックに到達する前に account = Account.first を実行するとします。両方のリクエストのメモリには 200 が入っています。

次に、1 つのリクエストでレコードがロックされ、残高が 100 に変更されます。しかし、レコードのロックが解除されると、2 番目のリクエストは残高を再度読み取ることはなく、古い値 200 を持つため、依然として競合状態が発生します。この場合。そこで私の質問は、競合状態を本当に回避するには、account = Account.first も with_lock ブロックでラップするべきではないでしょうか?

解決策

with_lock は、ドキュメントから、ブロックに屈する前にオブジェクトをリロードします。

ドキュメントの with_lock の例は、次のものと (ほぼ) 同等です。

account = Account.first
account.transaction do
  account.reload(lock: true)
  account.balance -= 100
  account.save!
end

リロード呼び出しにより、オブジェクトの属性がデータベースから確実に更新されます。