複数のユーザーが同時にデータを更新するシステムでは、「同じデータを同時に更新してしまう競合」が問題になります。
この問題に対処する方法として「楽観的ロック」と「悲観的ロック」があります。
本記事では、楽観的ロックの意味・仕組み・悲観的ロックとの違いを、排他制御・同時実行制御・バージョン管理・競合回避の観点からわかりやすく解説していきます。
データベース設計やWebアプリケーション開発に携わる方に特に参考にしていただける内容です。
楽観的ロックとは何か?基本的な概念と仕組み
それではまず、楽観的ロックの基本的な概念と仕組みについて解説していきます。
楽観的ロック(Optimistic Locking)とは、「競合は滅多に起きないだろう」という前提のもと、データ取得時にはロックをかけず、更新時に競合が起きていないかを確認する排他制御の方式です。
「楽観的」という名前の通り、「おそらく誰も同時に更新していないだろう」と楽観的に考えて処理を進め、最後に確認するという考え方です。
競合の確認には、主に「バージョン番号」や「タイムスタンプ」が使われます。
【楽観的ロックの基本的な流れ】
①データを取得する(この時点ではロックなし)
②取得時のバージョン番号(例:version=5)を記憶する
③データを編集する
④更新時に現在のバージョンが5であることを条件にUPDATEする
⑤バージョンが5のままなら更新成功→バージョンを6に更新
⑥バージョンが変わっていたら競合発生→ユーザーにエラーを通知
このバージョン番号を使った仕組みにより、ロックを保持するコストなしに競合を検出できます。
楽観的ロックの最大の特徴は、「読み取り中はリソースをロックしないため、同時実行性が高い」という点です。
悲観的ロックとの違い:仕組みと使い分け
続いては、悲観的ロックとの違いと使い分けについて確認していきます。
悲観的ロック(Pessimistic Locking)は、「競合が起きるかもしれない」という前提で、データ取得時点からロックをかけて他のユーザーの更新を防ぐ方式です。
| 比較項目 | 楽観的ロック | 悲観的ロック |
|---|---|---|
| ロックのタイミング | 更新時のみ確認 | データ取得時にロック |
| 競合の想定 | 競合は稀という前提 | 競合が起きうるという前提 |
| 同時実行性 | 高い | 低い |
| デッドロックリスク | 低い | 高い |
| 競合発生時の処理 | エラー通知・再試行 | ロック解放まで待機 |
| 向いている場面 | 競合が少ない読み取り多い処理 | 競合が多い更新中心の処理 |
楽観的ロックが向いているのは、読み取りが多く更新が少ないシステム・同時ユーザー数が多いWebアプリケーションなどです。
悲観的ロックが向いているのは、在庫管理・予約システムなど競合が頻繁に起きる可能性が高い業務系システムです。
競合の発生頻度と更新の重要度に応じて、適切なロック方式を選択することがシステム設計の重要なポイントとなります。
楽観的ロックのバージョン管理とタイムスタンプによる実装
続いては、楽観的ロックのバージョン管理とタイムスタンプによる実装について確認していきます。
楽観的ロックの実装方法は主に「バージョン番号方式」と「タイムスタンプ方式」の2種類があります。
【バージョン番号方式のSQL例】
— データ取得
SELECT id, name, version FROM products WHERE id = 1;
— 取得時にversion=5だった場合
— 更新時にversionが変わっていないかを確認しながら更新
UPDATE products SET name = ‘新商品名’, version = version + 1
WHERE id = 1 AND version = 5;
— 更新件数が0件なら競合発生(他のユーザーが先に更新した)
バージョン番号方式では、更新のたびにバージョンを1ずつ増やし、更新条件にバージョン番号を含めます。
更新件数が0件であれば、他のユーザーがすでに更新してバージョンが変わったことを意味します。
タイムスタンプ方式は、バージョン番号の代わりに最終更新日時を使って競合を検出します。
バージョン番号方式の方がタイムスタンプ方式より確実性が高いとされています。
タイムスタンプはミリ秒単位で同時更新が起きた場合に検出できないリスクがあるためです。
楽観的ロックのメリット・デメリットと競合発生時の対処法
続いては、楽観的ロックのメリット・デメリットと競合発生時の対処法を確認していきます。
| 観点 | 内容 |
|---|---|
| メリット① | ロックを保持しないため高い同時実行性が得られる |
| メリット② | デッドロックが発生しない |
| メリット③ | 読み取り処理のパフォーマンスが高い |
| デメリット① | 競合が多い場合、更新失敗とリトライが頻発しユーザー体験が低下する |
| デメリット② | 競合発生時のエラーハンドリングをアプリケーション側で実装する必要がある |
| デメリット③ | 競合が多い環境ではスループットが低下する可能性がある |
競合が発生した場合の対処法としては「リトライ(再試行)」が一般的です。
ユーザーに「他のユーザーが更新しました。最新データを確認してから再試行してください」と通知し、再度取得・編集・更新のフローを踏む実装が多く採用されています。
楽観的ロックを採用する際は、競合発生時のユーザー体験を丁寧に設計することが実用性のカギとなるでしょう。
まとめ
楽観的ロックは「競合は滅多に起きない」という前提でデータ取得時にロックをかけず、更新時にバージョン番号やタイムスタンプで競合を検出する排他制御方式です。
悲観的ロックと比較すると同時実行性が高くデッドロックリスクが低い一方で、競合頻度が高い環境では更新失敗が増えるデメリットもあります。
システムの特性・競合の発生頻度・更新の重要度に応じて、楽観的ロックと悲観的ロックを適切に使い分けることが設計の重要なポイントです。
バージョン番号を使った実装は比較的シンプルであり、多くのWebフレームワークでも標準サポートされています。
ぜひ本記事の内容を参考に、排他制御の設計に役立ててみてください。