新人に悲観ロックによる排他制御を体験してもらう

## 1. はじめに
新人に悲観ロックによる排他制御を説明する際、実際にPostgreSQLを操作して見せたデモの評判がよかったため、今回Qiitaの記事にしてみました。排他制御は並列実行のイメージが掴めないと知識だけで理解するのは難しいので、体験してみると多少は理解しやすくなるかと思います。

なお、排他制御の説明については、他の方の良い記事やWebサイトがあるのでそちらを参照ください。

* [排他制御のあれこれ](http://qiita.com/opengl-8080/items/fa22f46d7bf35fa45c21)
* [データベースさわったこと無い新人向けトランザクション入門](http://qiita.com/komattio/items/838ea5df68eb076e8099)
* [TERASOLUNA5.xのガイドラインの6.4. 排他制御](http://terasolunaorg.github.io/guideline/5.3.0.RELEASE/ja/ArchitectureInDetail/DataAccessDetail/ExclusionControl.html)


## 2. 悲観ロックを試してみる
### 2.1. 事前準備
今回は「PostgreSQL 9.3」を利用して悲観ロックを試してみます。
PostgreSQLのインストールについては他の方の記事を参考にしてみてください。

* [Windows環境でPostgreSQL](https://qiita.com/yuann/items/0a787818c370d971336e)
* [新人研修用（DB編）](https://qiita.com/__Bazooka__/items/d765bb9e4b70bf557e44)

1. スタートメニューの「PostgreSQL 9.3」->「pgAdmin III」メニューをクリックし、pgAdminを起動します。
2. pgAdminの「虫眼鏡（SQL）」メニューをクリックし、SQLを実行する画面を表示します。
3. SQLを並行して実行するため、SQLの実行画面を２つ表示してください。
4. 排他制御を実感するため、２つの画面は同時に確認できるように並べて表示してください。

<img src="https://qiita-image-store.s3.amazonaws.com/0/195748/08774388-e7bf-b387-b47a-eadb8b795778.jpeg" width="75%">

### 2.2. ロック取得待ちによる悲観ロック
1. 画面１、画面２の両方の「SQLエディタ」タブの左上の入力エリアに、以下のSQL文を入力します。
2. 画面１、画面２の順に、画面上部の三角ボタンを押下し、入力したSQLを実行します。

```sql:ロック取得待ちになるSQL（例）
-- トランザクションの開始
begin transaction;
-- select for updateでロック取得待ちの排他制御を掛ける
select * from customer where customer_code = '00000001' for update;
```

今回の例では`customer`テーブルとしていますが、**１レコード以上のデータが格納されていれば、検索対象のテーブルはなんでも構いません。
必ず主キー検索で１レコードだけ選択される検索条件を設定し、`for update`を付与してください。**
ちなみにSQLでは`--`で始まる行がコメントで、PostgreSQLでは`;`がSQLの１文の終わりを意味します。

| 画面１ | 画面２ |
|:-----:|:-----:|
|<img src="https://qiita-image-store.s3.amazonaws.com/0/195748/dd1f7375-452d-027e-324d-512189e31236.jpeg" width="100%">|<img src="https://qiita-image-store.s3.amazonaws.com/0/195748/98473d38-8c6b-e3c1-ecde-4ae625852422.jpeg" width="100%">|

1. 最初にSQLを実行した画面１にはすぐに取得レコードが表示されます。
2. 二番目にSQLを実行した画面２はレコードのロック取得で待ち状態になります。
3. 左下の状態が「クエリーの実行中。」、右下の処理時間が進んでいることからも実行中の待ち状態であることが分かるかと思います。

| 画面１ | 画面２ |
|:-----:|:-----:|
|<img src="https://qiita-image-store.s3.amazonaws.com/0/195748/90680262-33a8-83d8-e537-0b7d86310bc3.jpeg" width="100%">|<img src="https://qiita-image-store.s3.amazonaws.com/0/195748/0f1a751c-15d2-2fa6-a402-199b72585082.jpeg" width="100%">|

1. 画面１の「SQLエディタ」タブの左上の入力エリアに、トランザクションをロールバック（終了）させるためのSQL文である「`rollback;`」を入力します。
2. 画面１の画面上部の三角ボタンを押下し、入力したSQLを実行します。
3. 画面１のトランザクションが終了してロックが解放され、画面２でロックが取得できた時点で、画面２に取得レコードが表示されます。
4. この時点で画面２のクエリーが完了するため、左下の状態が「OK.」、右下に待ち時間も含めた処理時間が表示されます。画面の例では`41450ms`で約40秒も待っていたことになります。
5. これで検証は終わりです。画面２もロールバックしてください。

| 画面１ | 画面２ |
|:-----:|:-----:|
|<img src="https://qiita-image-store.s3.amazonaws.com/0/195748/b2252b5d-3f36-5ea3-705c-b45262a93a4e.jpeg" width="100%">|<img src="https://qiita-image-store.s3.amazonaws.com/0/195748/633ebcf2-1117-0930-8656-f02f25d5838b.jpeg" width="100%">|

### 2.3. ロックが取得できない場合はエラーとする悲観ロック
1. 画面１、画面２の両方の「SQLエディタ」タブの左上の入力エリアに、以下のSQL文を入力します。
2. 画面１、画面２の順に、画面上部の三角ボタンを押下し、入力したSQLを実行します。

```sql:ロックが取得できない場合はエラーとなるSQL（例）
-- トランザクションの開始
begin transaction;
-- select for update nowait でロック取得でエラーの排他制御を掛ける
select * from customer where customer_code = '00000001' for update nowait;
```

**前回との違いは`for update`の後ろに`nowait`が付いている点です。英単語の意味の通り、ロック待ちを行わないという意味のオプションです。**

| 画面１ | 画面２ |
|:-----:|:-----:|
|<img src="https://qiita-image-store.s3.amazonaws.com/0/195748/1872a0b6-e0d0-88de-162a-af3e58f453ed.jpeg" width="100%">|<img src="https://qiita-image-store.s3.amazonaws.com/0/195748/670f7936-6c7b-1716-9140-69c9b0fbbeaf.jpeg" width="100%">|

1. 最初にSQLを実行した画面１にはすぐに取得レコードが表示されます。
2. 二番目にSQLを実行した画面２ではロックが取得できないためエラーになります。つまり`nowait`を付けると、待ちではなくその場でエラーになるということです。
3. これで検証は終わりです。画面１、画面２のどちらもロールバックしてください。

| 画面１ | 画面２ |
|:-----:|:-----:|
|<img src="https://qiita-image-store.s3.amazonaws.com/0/195748/563575a9-8a3b-710e-422b-b8933b12f489.jpeg" width="100%">|<img src="https://qiita-image-store.s3.amazonaws.com/0/195748/6d862427-860e-3171-8613-f1e6900197b5.jpeg" width="100%">|

## 3. さいごに
今回はPostgreSQLを実際に操作し、悲観ロックによる排他制御を体験できるようにしてみました。悲観ロックがどういった動作なのか、`nowait`の有無がどのように影響するのかが理解できたかと思います。
なお、今回試してはいませんが、排他制御を理解するには以下のパターンも試すことをお勧めします。

* ロック取得中に`for update`の付いていないselect文を実行するとどうなるのか。
* ロック取得中に別のレコードを`select for update`で検索するとどうなるのか。

PostgreSQLには`for update`でロック待ちの時間を指定することはできませんが、Oracleなど他のデータベースでは待ち時間を指定することが可能なものも存在します。
また、レコードロックではなくテーブルロックで悲観ロックを実施すべき場面も出てくるかと思います。その場合も今回の検証と同様に、実際に試して動作を体験することをお勧めします。