良く分かるMySQL Innodbのギャップロック

# MySQLのロック
## ロックとはトランザクションの並列度を上げる為の並列スケジューリング方法の一つ
トランザクションをサポートしているデータベースにおいては、トランザクションの並列数を上げる事が性能アップの一つの方法。
他のトランザクションに更新して欲しくないデータだけにロックをかけて、ロックされたデータ以外を更新するトランザクションは並列で実行される。
## Innodbは行ロック？
Innodbは更新対象の行だけをロックする。と思っていると、意外な落とし穴にハマる。
その一つがギャップロック。
## ギャップロックを実際に起こしてみる
### サンプルテーブル
idとstrがあるだけのシンプルなテーブル。idがPKで1~5までは順番に、その後、10,20と飛んで行が入っている。
![スクリーンショット 2015-07-14 22.30.24.png](https://qiita-image-store.s3.amazonaws.com/0/62421/28626734-d45f-0358-c696-8f56dc3fbeaf.png "スクリーンショット 2015-07-14 22.30.24.png")
### 通常の行ロック
#### トランザクション１
select for updateでid=2の行を明示的にロック
![スクリーンショット 2015-07-14 22.33.22.png](https://qiita-image-store.s3.amazonaws.com/0/62421/ab867e83-70a7-fa1b-5b52-6fc472ef98ad.png "スクリーンショット 2015-07-14 22.33.22.png")
#### トランザクション２
id=1,3の更新は可能、id=2の更新は待たされる(画像分かりづらいが、ロックにより待たされているので応答が返ってこない)。
![スクリーンショット 2015-07-14 22.33.33.png](https://qiita-image-store.s3.amazonaws.com/0/62421/7d1b8e9c-9028-6529-2cfc-5e9a8d7dd0cb.png "スクリーンショット 2015-07-14 22.33.33.png")

これは通常の行ロックで、トランザクション１で触った行だけがロックされている。
### ギャップロック
#### トランザクション１
idが飛んでいる範囲の5~10をselect for updateしてみる。
行ロックの感覚でいると、selectされてきたid=5と10の行にだけロックがかかると思うが、、、
![スクリーンショット 2015-07-14 22.39.36.png](https://qiita-image-store.s3.amazonaws.com/0/62421/5a11f751-086c-d006-32e2-ac7181b22018.png "スクリーンショット 2015-07-14 22.39.36.png")
#### トランザクション２
id = 7に新しくinsertしてみると応答が返ってこない(ロックで待たされる)。
![スクリーンショット 2015-07-14 22.39.48.png](https://qiita-image-store.s3.amazonaws.com/0/62421/7851923a-e6e5-336f-322b-0e8c00d2c007.png "スクリーンショット 2015-07-14 22.39.48.png")

もともと存在しているのはid=5と10だけなので、その2行がロックされると思いきや、存在していないid=7にinsertしようとするとそこにもロックがかけられている。これがギャップロック。

### selectが空ぶってもギャップロックがかかる
#### トランザクション１
id = 6~9の範囲でselect for updateをする。つまり、どの行も実際には触っていない。
![スクリーンショット 2015-07-14 22.54.27.png](https://qiita-image-store.s3.amazonaws.com/0/62421/8051e3ca-9618-0151-5e6e-655b9e0fbd71.png "スクリーンショット 2015-07-14 22.54.27.png")
#### トランザクション２
そんな場合でもid = 7にinsertは出来ない。
![スクリーンショット 2015-07-14 22.54.44.png](https://qiita-image-store.s3.amazonaws.com/0/62421/2fa19473-7d8d-a2bf-b512-295c869ec004.png "スクリーンショット 2015-07-14 22.54.44.png")

一件どの行もロックする必要がなさそうに見えるselectの空振り時にもギャップロックがかかってしまう。

### 範囲検索じゃなくても空ぶったらギャップロック
#### トランザクション１
![スクリーンショット 2015-07-14 22.57.48.png](https://qiita-image-store.s3.amazonaws.com/0/62421/2f1077ac-e370-98c2-307a-811a0e86f2ae.png "スクリーンショット 2015-07-14 22.57.48.png")
#### トランザクション２
![スクリーンショット 2015-07-14 22.57.59.png](https://qiita-image-store.s3.amazonaws.com/0/62421/7ba11c7f-f35d-255b-e38b-3e20427b4b8a.png "スクリーンショット 2015-07-14 22.57.59.png")
範囲だろうと、id指定だろうと空振りでロックがかかる

### 範囲検索外でもギャップに入ってたらロックがかかる
#### トランザクション１
id > 9 でselect for update
![スクリーンショット 2015-07-14 22.59.23.png](https://qiita-image-store.s3.amazonaws.com/0/62421/b4a25a12-82f2-4317-252c-13390eeac166.png "スクリーンショット 2015-07-14 22.59.23.png")
#### トランザクション２
id = 7は一件select for update外にいるがギャップロックのせいでロックがかかる
![スクリーンショット 2015-07-14 22.59.38.png](https://qiita-image-store.s3.amazonaws.com/0/62421/3c47a5b2-5701-10cb-8209-ea3fba45959c.png "スクリーンショット 2015-07-14 22.59.38.png")

## 要するにギャップロックとは
インデックスとインデックスの間にかけられるロック。
インデックス単位でのロックを考えようとするとわりと分かりやすい仕組みかなと思う。
ロックの対象範囲を最小限にしつつ、トランザクション的に異常が起きないようにしようとすると確かにそうなる。
MySQL的には、5と10の間に6,7,8,9って入るなーなんて考えないので。
ちなみに、プライマリキーも何も無いテーブルで同じ事をするとテーブルロックがかかる。これもインデックス単位でロック範囲を絞り込みしている事を考えると自然。
要するにどこの範囲に更新されると不都合か、という事が全く分からないのでテーブル全体をロックするということ。

## 実際に起きる事故とか
例えば、100人ユーザーがいるサービスのユーザーテーブルで間違えて(バグで)id = 200に対してロックをかけてしまった場合、この場合のギャップはid > 100となるのでこのタイミングで新規入会してきたユーザーはロック待ちになる。ロックが解除されなければタイムアウトなどで会員登録が出来無いようなエラーになる可能性すらある。