🔒
【Rails】悲観ロックと楽観ロックの違いを理解する
はじめに
ロックとは?
ロックとは、複数のユーザーや処理が同じデータを同時に変更しようとしたときに、データの不整合を防ぐ仕組みです。
たとえば、ある在庫数をAさんとBさんが同時に減らそうとした場合、ロックがないと最終的な在庫が意図しない数になってしまう可能性があります。
トランザクションとは?
トランザクションとは、複数の処理を1つのまとまりとして扱い、すべてが成功するか、すべて失敗してロールバックするという仕組みです。
これにより、データの整合性や一貫性を保証することができます。
ロックの種類
悲観ロック(Pessimistic Lock)
悲観ロックは、「どうせ競合するはずだ」という前提で、データを読み込んだ時点でロックをかける手法です。他のトランザクションはそのデータにアクセスできなくなります。
Railsでの書き方
Book.transaction do
book = Book.lock.find(1)
book.title = "New Title"
book.save!
end
悲観ロックの動作検証(rails consoleでの手順)
前提準備
セッションA(ターミナル1)
Book.transaction do
book = Book.lock.find(1)
puts "A: ロック取得済み"
sleep 30
book.title = "散歩"
book.save!
puts "A: 更新完了"
end
セッションB(ターミナル2)
Book.transaction do
puts "B: ロック取得前"
book = Book.lock.find(1)
puts "B: ロック取得完了"
book.title = "昼寝"
book.save!
puts "B: 更新完了"
end
実行結果の確認
- セッションBは、セッションAが
save!してトランザクションを終了するまで**find(1)で待たされる** - ロックが解放された後、セッションBが処理を再開する
楽観ロック(Optimistic Lock)
楽観ロックは、「たぶん競合しないだろう」という前提で、ロックをかけずに処理を行い、更新時に競合していたら例外を投げてリトライする手法です。
Railsでの使い方
lock_version という integer 型カラムを用います。
# migration
add_column :books, :lock_version, :integer, default: 0, null: false
# controllerなど
book = Book.find(1)
book.title = "Updated title"
book.save! # => lock_version が変わっていたら ActiveRecord::StaleObjectError
競合の発生例
# ユーザーA
book_a = Book.find(1)
# ユーザーB
book_b = Book.find(1)
book_b.title = "From B"
book_b.save! # lock_version 0 → 1 に
# ユーザーAが保存しようとすると...
book_a.title = "From A"
book_a.save!
# => ActiveRecord::StaleObjectError が発生
リトライ処理の例
MAX_RETRIES = 3
retries = 0
begin
book.save!
rescue ActiveRecord::StaleObjectError
retries += 1
if retries <= MAX_RETRIES
book.reload
retry
else
raise "競合が解決できませんでした"
end
end
特徴
- データベースにロック負荷をかけない
- 競合が少ない場面で有効
- リトライ処理が必要
悲観ロックと楽観ロックの比較
| 項目 | 悲観ロック | 楽観ロック |
|---|---|---|
| 前提 | 競合するはず | 競合しないはず |
| ロックのタイミング | 読み取り時点 | 更新時点(バージョンチェック) |
| 実装方法 |
lock, with_lock, lock!
|
lock_version カラムを使用 |
| デッドロックの可能性 | あり | なし |
| 向いている場面 | 同時更新が頻繁に起きる | 稀にしか競合しない |
まとめ
- 悲観ロックは「安全第一」で先にロックをかけて他のトランザクションをブロック
- 楽観ロックは「高速優先」でロックせず、競合があったらエラーで検知
- 運用状況やデータの性質に応じて使い分けるのが重要です!
Discussion