排他制御（楽観ロック・悲観ロック）の基礎　


## 排他制御とは
共有資源（データやファイル）に対して複数のアクセスが見込まれる場合に、同時アクセスにより不整合が発生することを防ぐため、あるトランザクションが共有資源（データやファイル）にアクセスしている時は他トランザクションからはアクセスできないようにして直列に処理されるように制御すること。

■同時アクセスによる不整合の例
![同時アクセス.png](https://qiita-image-store.s3.amazonaws.com/0/23101/cdaea54e-59ac-74d4-3075-10157194e514.png)



■排他制御をすることで整合性を保つ
![排他制御.png](https://qiita-image-store.s3.amazonaws.com/0/23101/ea09ae73-69b7-e738-e744-e96f56fd2f47.png)

## 排他制御の方式
排他制御の実現方式はいくつか存在するが、ここでは代表的な楽観ロック（楽観的排他制御）と悲観ロック（悲観的排他制御）を紹介する。

### 楽観ロック（楽観的排他制御）
楽観ロックとは、めったなことでは他者との同時更新は起きないであろう、という楽観的な前提の排他制御。データそのものに対してロックは行わずに、更新対象のデータがデータ取得時と同じ状態であることを確認してから更新することで、データの整合性を保証する方式。楽観ロックを使用する場合は、更新対象のデータがデータ取得時と同じ状態であることを判断するために、Versionを管理するためのカラム(Versionカラム)を用意する。更新時の条件として、データ取得時のVersionとデータ更新時のVersionを同じとすることで、データの整合性を保証することができる。

![楽観ロック.png](https://qiita-image-store.s3.amazonaws.com/0/23101/380494e0-3884-c2f3-8896-a25af91b4cfc.png)

更新対象のデータがデータ取得時と同じ状態であることを判断するためのカラムを、「ロックキー」と呼ぶ。ロックキーは、Versionカラム以外にも更新日時等のタイムスタンプを用いることもできる。ただし、タイムスタンプを秒単位までしか保有していない場合、同一秒に複数の操作を行われた時に楽観ロックの判定ができなくなる。また、より精度の高いミリ秒まで保有していたとしても同様のことが言える。つまりタイムスタンプではこの懸念は払拭できないためロックキーは極力Versionカラムを利用した方が無難。
また、検知のタイミングが業務終了時での検知となるため、画面入力に時間がかかる業務の場合は最初からやり直しになってしまい、結果として時間のロスとなってしまう。

### 悲観ロック（悲観的排他制御）
他者が同じデータに頻繁に変更を加えるであろう、という悲観的な前提の排他制御。更新対象のデータを取得する際にロックをかけることで、他のトランザクションから更新されないようにする方式。悲観ロックを使用する場合は、トランザクション開始直後に更新対象となるレコードのロックを取得する。ロックされたレコードは、トランザクションが、コミットまたはロールバックされるまで、他のトランザクションから更新されないため、データの整合性を保証することができる。

![asdada.png](https://qiita-image-store.s3.amazonaws.com/0/23101/b7b560f0-57f6-d36d-82fe-553ece6a4cb1.png)

データのロックはRDBMSの「SELECT 〜 FOR UPDATE」を利用して実現されるのが一般的。悲観ロックでは、ロックの解放漏れがあると、いつまで経っても他者が操作できないということに繋がるため、データ更新後はロックの解放を必ず行うこと。また、確実にロックを解放するのは難しいという特性も持っている。例えば処理中に、

* Webアプリケーションでブラウザの×ボタンが押された
* Webブラウザが強制シャットダウンした
* 操作しているPCを強制シャットダウンした

等が発生すると、場合によってはロックしたままの状態となってしまう。これら全てをアプリケーションの設計で制御するのは困難なため、悲観ロックを採用する場合は、

* 管理者であればロックを解放できる機能を設ける
* ロックしているユーザであれば、ロックを再取得できるようにする
* ロックしているユーザのセッションタイムアウトのタイミングで、そのユーザがロックしている全てのデータのロックを解放する
* 長時間ロックされているデータはロック解放するような機能を設ける

等の仕組みを設ける等、何らかの対応策を用意しておく必要がある。

## 排他制御方式の比較
![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/23101/c9415ed8-63c1-53ef-f385-34221db1d735.png)

## 排他制御の長さ
排他制御している時間が長ければ長いほど、システムの利便性が下がってしまう。そのため、不都合や不整合が発生しない範囲で可能な限り短くすることが鉄則。

## バッチ処理とオンライン処理の排他制御
オンライン処理だけでなくバッチ処理もあるシステムの場合、オンライン処理でのデータ更新とバッチ処理でのデータ更新が同時に行われるケースも考える必要がある。また、オンライン処理とバッチ処理の排他制御は難易度が高いため、以下の順で方針を検討する。

方針1.　オンライン処理とバッチ処理で競合が発生しないよう処理を分ける
方針2.　（1がダメなら）バッチ処理でも排他制御を行う

### 方針1. オンライン処理とバッチ処理で競合が発生しないよう処理を分ける
#### 1. 時間帯で処理を分ける
![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/23101/39129e59-a69e-5ddd-a65e-b275d124bef3.png)
オンライン処理とバッチ処理の時間帯を決めておくことで、そもそも競合を発生させないようにする方式。ただ、この方式は24時間稼働が要件のシステムでは適用できない。

#### 2. データで処理を分ける
![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/23101/4bf657c2-c00b-02e0-e6e5-2c40a9ccc367.png)
ステータスのようなものを設け、オンライン処理の対象データなのかバッチ処理の対象データなのかを分けることで、そもそも競合を発生させないようにする方式。

### 方針2. バッチ処理でも排他制御を行う
オンライン処理とバッチ処理で競合が発生しないよう処理を分けることができない場合、バッチ処理でも排他制御を行う必要がある。この時の排他制御方式は大きく分けて3つ存在する。

#### 1. オンライン処理のロック解除を待ってから排他制御を行う（オンライン優先）
オンライン処理を優先させたい場合はこの方式を採用する。ただし、バッチの開始が遅れる分、終了も遅くなる。

#### 2. オンライン処理のロック解除を待たずバッチ処理で強制的にロックを解除する（バッチ優先）
バッチ優先にしたい場合はこの方式を採用する。この場合、バッチ処理は時間通り開始することができるが、オンライン処理では排他エラーが発生してしまう。

#### 3. バッチ処理でロックできたもののみ処理対象とする
バッチでロックが取得できたもののみ処理対象とする方式。対象データを全て取得し（ここではロックしない）、取得したデータを1件1件ロックしながらデータを処理する。ロックが取得できなかったものは処理スキップし次回のバッチ処理対象に回す等で対応する。

どの方式も間違いではないため、業務要件を鑑みてしっかりと検討したうえで一番マッチする方式を採用する。


以上です。

## 参考文献
[図解でなっとく! トラブル知らずのシステム設計 エラー制御・排他制御編](https://www.nikkeibp.co.jp/atclpubmkt/book/18/266680/)