InnoDBのロックに関する用語の確認

## ロックの分類

InnoDBのロックは「タイプ[^1]」「モード」「精密なモード[^2]」で分類できる。


### ロックのタイプ[^1]（lock type）

* [テーブルロック](https://dev.mysql.com/doc/refman/5.6/ja/glossary.html#glos_table_lock)（[table lock](https://dev.mysql.com/doc/refman/5.6/en/glossary.html#glos_table_lock)）
* [行ロック](https://dev.mysql.com/doc/refman/5.6/ja/glossary.html#glos_row_lock)（[row lock](https://dev.mysql.com/doc/refman/5.6/en/glossary.html#glos_row_lock)）

ロックのタイプは2種類ある。InnoDBに限った概念ではない。
InnoDBではなるべくテーブルロックを使わず、行ロックを使うようにしているそうだ。実際、明示的にテーブルロックを利用しようという場面はほとんどないと思う。

たまに勘違いしている人をみかけるが、**「行ロックの範囲がテーブル全体である」ことと「テーブルロック」は異なる。**前者はあくまでも行ロックである。

システムによっては、メモリ節約のために「大量の行ロック」を「テーブルロック」に自動的に変更することもあるらしいが、InnoDBでは不要だそうだ。（参考：[ロックのエスカレーション](https://dev.mysql.com/doc/refman/5.6/ja/glossary.html#glos_lock_escalation)）

また、InnoDBでは、行ロックだけを使っているつもりでも内部的にはテーブルロックの仕組みが使われている。（詳しくはインテンションロックの項目を参照。）


### [ロックのモード](https://dev.mysql.com/doc/refman/5.6/ja/glossary.html#glos_lock_mode)（[lock mode](https://dev.mysql.com/doc/refman/5.6/en/glossary.html#glos_lock_mode)）

* [共有ロック](https://dev.mysql.com/doc/refman/5.6/ja/glossary.html#glos_shared_lock)（[shared lock](https://dev.mysql.com/doc/refman/5.6/en/glossary.html#glos_shared_lock)）
* [排他ロック](https://dev.mysql.com/doc/refman/5.6/ja/glossary.html#glos_exclusive_lock)（[exclusive lock](https://dev.mysql.com/doc/refman/5.6/en/glossary.html#glos_exclusive_lock)）

ロックのモードは2種類ある。InnoDBに限った概念ではない。
共有ロックは`(S)`、排他ロックは`(X)`と表記されることがある。


### ロックの精密なモード[^2]（precise mode）

* [レコードロック](https://dev.mysql.com/doc/refman/5.6/ja/glossary.html#glos_record_lock)（[record lock](https://dev.mysql.com/doc/refman/5.6/en/glossary.html#glos_record_lock)）
* [ギャップロック](https://dev.mysql.com/doc/refman/5.6/ja/glossary.html#glos_gap_lock)（[gap lock](https://dev.mysql.com/doc/refman/5.6/en/glossary.html#glos_gap_lock)）
* [ネクストキーロック](https://dev.mysql.com/doc/refman/5.6/ja/glossary.html#glos_next_key_lock)（[next-key lock](https://dev.mysql.com/doc/refman/5.6/en/glossary.html#glos_next_key_lock)）

ロックの精密なモードは、InnoDBに特有の概念（というより、実装方法からくる分類）。行ロックだけの分類で、テーブルロックには関係ない。

* レコードロックは、その名の通り、レコードをロックする。
* ギャップロックは、その名の通り、ギャップ（隙間）をロックする。レコードとレコードの間をロックすることで、その空間に新しいレコードが挿入されることを防ぐ。
* ネクストキーロックは、レコードロックと、そのレコードの前の空間に対するギャップロックをセットにしたロック。



## 先頭のレコードより前、末尾のレコードより後

* [最大下限レコード](https://dev.mysql.com/doc/refman/5.6/ja/glossary.html#glos_infimum_record)（[infimum record](https://dev.mysql.com/doc/refman/5.6/en/glossary.html#glos_infimum_record)）
* [最小上限レコード](https://dev.mysql.com/doc/refman/5.6/ja/glossary.html#glos_supremum_record)（[supremum record](https://dev.mysql.com/doc/refman/5.6/en/glossary.html#glos_supremum_record)）

それぞれ、先頭レコードよりも前にある疑似レコードと、末尾レコードより後にある疑似レコードを意味する。「先頭レコードより前」や「末尾レコードより後」のギャップロックを表現するために使われる。
日本語の方はあまり使わないと思う。

**先頭レコードより前への挿入をブロックするには、先頭レコードとinfimumレコードのギャップをロックする**必要がある。
また、**末尾レコードより後ろへの挿入をブロックするには、末尾レコードとsupremumレコードのギャップをロック**する必要がある。

「先頭レコードのネクストキーロック」を使えば「先頭レコードとinfimumのギャップ」もロックされる。そのため、infimumは実際にはあまり出番がないようだ。

しかし、「末尾レコードのネクストキーロック」では「末尾レコードとsupremumレコードのギャップ」をロックすることはできない。そのため、`innodb_lock_monitor`で見ると`supremum`の表示はよくある。



## [インテンションロック](https://dev.mysql.com/doc/refman/5.6/ja/glossary.html#glos_intention_lock)（[intention lock](https://dev.mysql.com/doc/refman/5.6/en/glossary.html#glos_intention_lock)）

テーブルに対するロックで、これにも2種類のモードがある。

* インテンション共有ロック（intention shared lock）：`(IS)`と表記される
* インテンション排他ロック（intention exclusive lock）：`(IX)`と表記される

インテンションロックは、行ロックとテーブルロックを共存させるための実装上の都合で存在するロック。

動作としては、トランザクション`Tx1`がテーブル`test`上で行ロック(X)を取得しようとする場合、**先にテーブル`test`に対するテーブルロック(IX)の取得を試みる**。（同様に、行ロック(S)を取得しようとする場合ならば、テーブルロック(IS)を試みる。）

このとき、テーブル`test`が**他のセッションですでに`LOCK TABLES test READ;`で明示的にテーブルロック(S)されていた**とする。すると、**テーブルロック(S)とテーブルロック(IX)は競合する**ので、**`Tx1`はテーブルロック(IX)を取得できない。よって、`Tx1`は行ロック(X)も取得できない。**
これにより、テーブルロック済みのテーブル上のレコードに対する、競合する行ロックの取得を防げる。

別のケースとして、**他のトランザクション`Tx2`がテーブル`test`上で行ロック(S)を取得していた**とする。つまり、**`Tx2`はテーブル`test`に対するテーブルロック(IS)を取得済み**ということになる。この場合、**テーブルロック(IS)とテーブルロック(IX)は競合しない**ので、**`Tx1`もテーブルロック(IX)を取得できる。**
その後、`Tx1`は、`Tx2`が行ロック(S)していないレコードの範囲についてならば、行ロック(X)を取得できるだろう。

このように、行ロック時にもテーブルロックを使用して2段階にすることで、テーブルロックと行ロックを解りやすく共存できるようにしている。

[MySQL :: MySQL 5.6 リファレンスマニュアル :: 14.2.3 InnoDB のロックモード](https://dev.mysql.com/doc/refman/5.6/ja/innodb-lock-modes.html#idm140512489037360)より、ロックの対応表を引用

|    |  X   |  IX  |  S   |  IS  |
|:--:|:----:|:----:|:----:|:----:|
| X  | 競合 | 競合 | 競合 | 競合 |
| IX | 競合 | 互換 | 競合 | 互換 |
| S  | 競合 | 競合 | 互換 | 互換 |
| IS | 競合 | 互換 | 互換 | 互換 |

* インテンション同士は（共有・排他を問わず）競合しない
* 共有同士は（インテンションかどうかを問わず）競合しない


## 脚注

[^1]: ドキュメントにはない表記。InnoDBのソース上の[`LOCK_TABLE`定数と`LOCK_REC`定数の定義してあるところにコメントで`/** Lock types */`と書いてあった](https://github.com/mysql/mysql-server/blob/3a4e7bc7c416888fda897239c03549c2929bb64c/storage/innobase/include/lock0lock.h#L856)ため、また、[「ロックが`LOCK_TABLE`か`LOCK_REC`か返す関数」が`lock_get_type_low`](https://github.com/mysql/mysql-server/blob/3a4e7bc7c416888fda897239c03549c2929bb64c/storage/innobase/include/lock0priv.ic#L40)だったため、こう呼んでいる。
[^2]: ドキュメントにはない表記。InnoDBのソース上の[`LOCK_GAP`や`LOCK_REC_NOT_GAP`が定義してあるところにコメントで`Precise modes`と書いてあった](https://github.com/mysql/mysql-server/blob/3a4e7bc7c416888fda897239c03549c2929bb64c/storage/innobase/include/lock0lock.h#L868)ため、こう呼んでいる。