楽観ロックと悲観ロックのおさらい。

ChatGPTに聞いてみた

楽観的ロック(Optimistic Locking)と悲観的ロック(Pessimistic Locking)は、データベースや他のトランザクションシステムでの競合状態を制御するための2つの主要なアプローチです。これらのアプローチの主な違いと特性を以下に示します。

悲観的ロック(Pessimistic Locking)

  1. 前提: トランザクションがデータにアクセスするときに他のトランザクションが同時にそのデータにアクセスする可能性が高いと仮定します。
  2. 動作: データにアクセスする際にすぐにロックをかけることで、他のトランザクションが同じデータにアクセスするのを防ぎます。
  3. 結果: ロックがかかっている間、他のトランザクションはそのデータにアクセスできないため、待機するか別のアクションを取る必要があります。
  4. 使用ケース: トランザクションの競合が頻繁に発生する可能性が高い場合や、データの整合性が非常に重要な場合に適しています。

楽観的ロック(Optimistic Locking)

  1. 前提: トランザクションがデータにアクセスするときに他のトランザクションが同時にそのデータにアクセスする可能性は低いと仮定します。
  2. 動作: データを読み取る際にロックはかけませんが、データを更新する際に元のデータが変更されていないか確認します。変更されていた場合、競合が発生したとみなし、更新は失敗します。
  3. 結果: 競合が発生した場合、トランザクションはリトライするかエラーハンドリングを行う必要があります。
  4. 使用ケース: トランザクションの競合が稀で、ロックによるオーバーヘッドを避けたい場合や、スケーラビリティが重要な場合に適しています。

まとめ

  • 悲観的ロックは競合を防ぐためにデータにアクセスする前にロックをかけますが、楽観的ロックは競合を検出するためにデータの更新時にチェックを行います。
  • 適切なロックの方法は、アプリケーションの要件やトランザクションの特性に応じて選択する必要があります。

Railsの場合

楽観的ロックを使うには、テーブルにlock_versionという名前のinteger型カラムが必要です。Active Recordは、レコードが更新されるたびにlock_versionカラムの値を1ずつ増やします。更新リクエストが発生したときのlock_versionの値がデータベース上のlock_versionカラムの値よりも小さい場合、更新リクエストは失敗し、以下のようにActiveRecord::StaleObjectErrorエラーが発生します。

c1 = Customer.find(1)
c2 = Customer.find(1)

c1.first_name = "Sandra"
c1.save

c2.first_name = "Michael"
c2.save # ActiveRecord::StaleObjectErrorが発生

Active Record クエリインターフェイス - Railsガイド

悲観的ロックでは、データベースが提供するロック機構を利用します。リレーションの構築時にlockを使うと、選択した行に対する排他的ロックを取得できます。lockを用いているリレーションは、デッドロック条件を回避するために通常トランザクションの内側にラップされます。

以下に例を示します。

Book.transaction do
  book = Book.lock.first
  book.title = 'Algorithms, second edition'
  book.save!
end
バックエンドがMySQLの場合、上のセッションによって以下のSQLが生成されます。
SQL (0.2ms)   BEGIN
Book Load (0.3ms)   SELECT * FROM books LIMIT 1 FOR UPDATE
Book Update (0.4ms)   UPDATE books SET updated_at = '2009-02-07 18:05:56', title = 'Algorithms, second edition' WHERE id = 1
SQL (0.8ms)   COMMIT

ref. Active Record クエリインターフェイス - Railsガイド