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