排他制御とは?
複数人の同時アクセスでデータの不整合が生じてしまわないようにすることです。排他制御には主に下記2種類があります。
- 楽観ロック
- 悲観ロック
楽観ロック
「滅多なことじゃ他者による更新なんて起きないだろう」という楽観的な前提のもとにロックをかけること。なので、他者による更新が頻繁に起こることが見込まれるリソースに対しては楽観ロックは向かない。「処理が終わってから」じゃないと他者と競合していることがわからないので利用者にとってはストレスになってしまうためです。
具体的にはテーブルにロックキーを用意します。このロックキーを利用して下記の流れで制御を行います。
- 画面遷移時に一旦データを参照してロックキーを得る。
- ユーザーが画面上で更新情報を記述する。
- ユーザーが更新ボタンを押し、画面遷移前のバージョンと一致していたら更新処理を実施する。
- バージョンが一致していない場合は、登録エラーにして再度更新を促す画面に遷移させる。
ロックキーの種類
バージョン
バージョンという列を新たに作りそこで比較します。
メリット
- 確実性が高い
- 業務データに依存しない
デメリット
- 新たにカラムが必要
- 更新時にバージョンの更新が必要
タイムスタンプ
更新対象の列の更新日時を比較します。
メリット
- 新たにカラムを追加する必要がない。
デメリット
- 更新日時の精度が低い場合は確実性に欠ける。
全列データ
更新対象の全列のデータを比較します。カラム追加もできず、更新日時も追加できないという要件の場合はこれを選ぶしかないでしょう。
メリット
- カラム追加が不要
- 排他制御のために特別な更新も不要
デメリット
- パフォーマンスが悪い
楽観ロックのSQLの流れ
楽観ロックのSQL発行においても2種類の流れがあります。どちらにもメリデメがありますが基本的には「SELECT(FOR UPDATE)→UPDATE」方式が選ばれることが多いでしょう。
SELECT(FOR UPDATE)→UDPATE
SELECTして問題ないことがわかればUPDATEします。SELECTする際に「FOR UPDATE」とつけることで他の人は更新することができなくなります。
UPDATE where バージョン=xx
メリット
SQL発行回数を減らせる。
デメリット
- 業務チェックしてからになり、更新タイミングが後なので業務チェックが無駄になる。
- 更新結果が0件だった場合に業務処理上の理由によるものか、ロックキーによるものかの判断が難しい。(例えば、そもそもデータがなかった場合等)
悲観ロック
楽観ロックと違って作業開始時に競合を検知することができるので利用者のストレスが減ります。悲観ロックの流れは下記のようになります。先に画面にアクセスした人が更新の権利を得ます。
- 作業者Aが更新画面にアクセスし、すでに誰かにロックされていないかチェックする。(SELECT FOR UPDATE)
- 誰かにロックされていなければ作業者Aは対象のレコードにロックをかける。
- 作業者Bが一覧画面を開く(もしくはすでに開いている状態ならばアクセスする)と誰かが更新中とエラーになる。
- 作業者Aはロックしているのが自分かどうかチェックしてロックの解放及び、データの更新処理を実施する。
なお、4でロックしているのが自分かどうかチェックする理由としては、運用等で勝手に誰かが更新している可能性があるためです。
悲観ロックの注意点
途中で更新処理をやめてしまった場合でもロックを解除状態にすることを忘れないようにすること。色々な場面が想定できます。
- 作業者が一覧画面に戻った場合
- 作業者が「閉じる」ボタンを押した場合
- 作業者がログオフした場合
- 作業者がPCをシャットダウンした場合
- 作業者がセッションタイムアウトした場合
上記の中にはどうしようもない場合もあるので、下記のような対処法が取られたりします。
- 管理者がロックを解除できるような画面を用意する。
- 深夜のバッチで全ロックを自動で解除する。
- ロックしているユーザーであれば再度ロックを取得できるようにする。
- ユーザーのセッションタイムアウトのタイミングでそのユーザーが保持しているロックを全て解除する。
- 長時間ロックされている場合は自動的にロックを解除する仕組み
実際には、ロックの解除に時間をかけたくない場合がほとんどですので、1か3が採用されていることが多いでしょう。
悲観ロックで用意するテーブルの列
悲観ロックの対象のデータには下記のような列を用意することが一般的でしょう。
- ロックしているユーザー
- ロックの状態
- どの端末からのアクセスか(セッションID等)
楽観ロックと悲観ロックどちらを使えば良いか?
上記内容を比べると悲観ロックのほうが優れているように思えるかもしれませんが、悲観ロックはあらゆる場合を想定して適切なロック解除スキルが求められるので実装難易度が高いです。なので、よほどの機能でない限りは現実的には楽観ロックが採用されるケースが多いです。
楽観ロックを使う場合
- 競合があまり発生しない場合
- やり直したとしても時間があまりかからない業務の場合
悲観ロックを使う場合
- 複数人で更新するのが前提の場合
- 競合でやり直しが難しい or 時間がかかる業務の場合
- 具体的に言えば、お金の取引や重要文書の送付等の業務を複数人でやる業務はこちらを使います。
なお、実装コストは悲観ロックの方が高いということも考慮に入れておくと良いでしょう。
複雑な排他制御
同じテーブルに複数の種類の排他制御をかけてしまっているケース
同じテーブル内で数種類の排他制御が入り混じる場合があります。その場合排他制御自体が成り立たなくなるケースもあります。
- 楽観ロック
- 悲観ロック
- 更新時の行ロックのみ(SELECT FOR UPDATE)
例えば、商品テーブルがあったとして、「商品名」、「在庫数」のカラムがあり在庫数はユーザーが行ロックで更新して、商品名を管理者が楽観ロックで更新した場合はただの行ロックではバージョン等は管理していないので上書きしてしまう可能性があります。
対策
「商品テーブル」と「在庫テーブル」というように、業務上で同じロックをする単位にテーブルも分割するようにすると良いでしょう。
登録処理の排他制御
更新処理は問題なく排他制御をかけることが可能だとは思いますが、登録処理も排他制御をかけたい場合もあると思います。その場合は、さらに上の階層のテーブルを作って、その上の階層のテーブルに排他制御をかけて対応しましょう。
この記事へのコメントはありません。