Edited at

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

More than 1 year has passed since last update.


MySQLのロック


ロックとはトランザクションの並列度を上げる為の並列スケジューリング方法の一つ

トランザクションをサポートしているデータベースにおいては、トランザクションの並列数を上げる事が性能アップの一つの方法。

他のトランザクションに更新して欲しくないデータだけにロックをかけて、ロックされたデータ以外を更新するトランザクションは並列で実行される。


Innodbは行ロック?

Innodbは更新対象の行だけをロックする。と思っていると、意外な落とし穴にハマる。

その一つがギャップロック。


ギャップロックを実際に起こしてみる


サンプルテーブル

idとstrがあるだけのシンプルなテーブル。idがPKで1~5までは順番に、その後、10,20と飛んで行が入っている。

スクリーンショット 2015-07-14 22.30.24.png


通常の行ロック


トランザクション1

select for updateでid=2の行を明示的にロック

スクリーンショット 2015-07-14 22.33.22.png


トランザクション2

id=1,3の更新は可能、id=2の更新は待たされる(画像分かりづらいが、ロックにより待たされているので応答が返ってこない)。

スクリーンショット 2015-07-14 22.33.33.png

これは通常の行ロックで、トランザクション1で触った行だけがロックされている。


ギャップロック


トランザクション1

idが飛んでいる範囲の5~10をselect for updateしてみる。

行ロックの感覚でいると、selectされてきたid=5と10の行にだけロックがかかると思うが、、、

スクリーンショット 2015-07-14 22.39.36.png


トランザクション2

id = 7に新しくinsertしてみると応答が返ってこない(ロックで待たされる)。

スクリーンショット 2015-07-14 22.39.48.png

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


selectが空ぶってもギャップロックがかかる


トランザクション1

id = 6~9の範囲でselect for updateをする。つまり、どの行も実際には触っていない。

スクリーンショット 2015-07-14 22.54.27.png


トランザクション2

そんな場合でもid = 7にinsertは出来ない。

スクリーンショット 2015-07-14 22.54.44.png

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


範囲検索じゃなくても空ぶったらギャップロック


トランザクション1

スクリーンショット 2015-07-14 22.57.48.png


トランザクション2

スクリーンショット 2015-07-14 22.57.59.png

範囲だろうと、id指定だろうと空振りでロックがかかる


範囲検索外でもギャップに入ってたらロックがかかる


トランザクション1

id > 9 でselect for update

スクリーンショット 2015-07-14 22.59.23.png


トランザクション2

id = 7は一件select for update外にいるがギャップロックのせいでロックがかかる

スクリーンショット 2015-07-14 22.59.38.png


要するにギャップロックとは

インデックスとインデックスの間にかけられるロック。

インデックス単位でのロックを考えようとするとわりと分かりやすい仕組みかなと思う。

ロックの対象範囲を最小限にしつつ、トランザクション的に異常が起きないようにしようとすると確かにそうなる。

MySQL的には、5と10の間に6,7,8,9って入るなーなんて考えないので。

ちなみに、プライマリキーも何も無いテーブルで同じ事をするとテーブルロックがかかる。これもインデックス単位でロック範囲を絞り込みしている事を考えると自然。

要するにどこの範囲に更新されると不都合か、という事が全く分からないのでテーブル全体をロックするということ。


実際に起きる事故とか

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