PHP
楽観的排他制御と悲観的排他制御について(楽観ロックのサンプル付き)
えだ
更新日:2021/08/12
えだです。
排他制御について非エンジニアだと理解は深くありません。
そこで説明資料とともに知識浅なエンジニアにも理解してもらえるよう説明資料として起こしてみました。
ついでなので、終わりにはエンジニア向けに楽観的排他制御のサンプルプログラムも入れています。
まずは手っ取り早くイメージで説明しますね。
シナリオ
データベースの在庫テーブルを操作して、複数の人が在庫を消費(減算)する機能を想定します。
最初10個ある状態より、Aさんが2個、Bさんが3個を消費したら、結果は5個となるはずです。
これをシステム処理に置き換えてみるとこうなります。
- 画面描画時に在庫を表示する
- 変更後の値を入力して更新する
不整合となる例
しかし排他制御が導入されていない状況ではこのように不整合な状態になります。
Aさん:10個から8個に変更 →正常終了
Bさん:10個から7個に変更 →正常終了
結果、在庫7個。
そこで整合性を保つには排他制御が必要となります。
排他制御には大きく2種類あります。
悲観的排他制御
通称、悲観ロック。
読み取り時にロックをかけてしまいます。
select for update というやつです。
メリットはBさんのオペレーションロスが無いこと。
デメリットはロック設計の難しさです。例えばAさんのロック解除忘れも考慮が必要です。
帳票作業などオペレーションタイムが短いシステムに適しています。
楽観的排他制御
通称、楽観ロック。
更新時に取得時と同じか確認してから更新するようにします。
同じであれば正常終了させ、相違していれば異常終了とするわけです。
メリットは複数ユーザが並行してオペレーションできることです。
デメリットはデータの衝突が発生した場合にBさんのオペレーションロスが発生します。
同時作業の頻度が少ないシステムに適しています。
楽観的排他制御の具体的なサンプル
楽観的排他制御について具体的にどう書けばよいか、サンプルとなるデータベースとソースコードを示しておきます。
コードはPHP Codeigniter 4で書いていますので皆さんが利用する言語で読み直してください。
データベース
stock_id | stock_nm | stock_cnt | version |
1 | チョコ | 100 | 3 |
2 | アメ | 150 | 5 |
3 | グミ | 80 | 1 |
取得処理
1 |
$stock = $stockModel->where(“stock_id”, $stock_id)->find(); |
ビュー
1 |
<input type="hidden" name="version" value="{$stock.version}"> |
更新処理
1 2 3 4 5 6 |
$stock_now = $model->where(“stock_id”, $request->stock[‘stock_id’])->find(); if ($request->stock[‘version’] === $stock_now[‘version’]) { $model->update($stock_id, array(“stock_cnt”, $request->stock[‘stock_cnt’])); } else { throw new Exception(‘排他制御エラー:あなたの前に誰かが変更しちゃいましたよ’); } |
解説
versionを取得しておいてビューで隠し持っておきます。
更新処理の直前にて取得versionと今のversionを比較して差異ありならエラー表示します。
もちろん差異なしなら更新して終了です。
おわりに
楽観ロックはしっかりとプログラム設計していれば容易に導入することができます。
なるべく個別のビジネスロジックでは書くより、システムのアーキテクト設計でフレームワークに組み込めるとスムーズですね。