MySQLのロックについて公式ドキュメントを読みながら動作検証してみた〜行レベルロック: インテンションロック〜

データベースを使っていてロックの存在を知らない方はいないと思いますが、実際にプログラムからデータベースを操作する時に明示的にロックを意識することはほとんどありません。

たいていの場合、トランザクションだけ気をつけていたらDBMSが適切にロックを取得＆開放してくれます。

私もデッドロックなどロック関連の障害が発生した場合に調査のために学習するのですが、対応が終わってしばらくしたら細かい仕様は忘れてしまいますw

毎回忘れるのはもったいないので、ドキュメントを読み直して自分なりにまとめておこうと思い、この記事を書くことにしました。

当初、1つの記事にまとめようと思いましたが、書いているうちにボリュームが大きくなってしまったので下記に分割することにしました。

この記事は「行レベルロック: インテンションロック」です。

* [ロックを確認するためのテーブルや設定](https://qiita.com/ham0215/items/89fc277850d32f5b17cd)
* [テーブルレベルロック](https://qiita.com/ham0215/items/f53dda06dd43e6be12c9)
* [行レベルロック: 共有ロック(S) / 排他ロック(X)](https://qiita.com/ham0215/items/b9efc718670b1d2d48c1)
* 行レベルロック: インテンションロック (この記事)
* [行レベルロック: レコードロック / ギャップロック / ネクストキーロック / 他](https://qiita.com/ham0215/items/99679d499869365446ec)

# MySQLバージョン

執筆時点(2021年9月)の最新バージョン、`8.0.26`を利用します。

# 検証方法

今回の検証はmysqlコマンドを使って行います。

https://dev.mysql.com/doc/refman/5.6/ja/mysql-commands.html

トランザクションを開始するために`BEGIN`コマンドを使います。
トランザクションは`COMMIT` or `ROLLBACK`を実行するまで継続されます。

https://dev.mysql.com/doc/refman/5.6/ja/commit.html

なお検証には下記のpiyosテーブルを使用します。

## piyos

インデックス有無によるロック範囲の確認用にインデックスありのidx_numとインデックスなしのnumカラムを持っています。
他のテーブルと関連していません。

| Name | Type | Default | Nullable | Extra Definition |
| ---- | ---- | ------- | -------- | --------------- |
| id | bigint |  | false | auto_increment |
| idx_num | int | 0 | false |  |
| num | int | 0 | false |  |
| name | varchar(255) |  | true |  |
| created_at | datetime(6) |  | false |  |
| updated_at | datetime(6) |  | false |  |

* インデックス

| Name | Definition |
| ---- | ---------- |
| idx_num | KEY idx_num (idx_num) USING BTREE |
| PRIMARY | PRIMARY KEY (id) USING BTREE |


検証のため下記のデータを入れています。

```
mysql> select * from piyos;
+----+---------+-----+-------+----------------------------+----------------------------+
| id | idx_num | num | name  | created_at                 | updated_at                 |
+----+---------+-----+-------+----------------------------+----------------------------+
|  1 |       1 |   1 | piyo1 | 2021-10-01 08:50:52.882406 | 2021-10-01 08:50:52.882406 |
|  3 |       3 |   3 | piyo3 | 2021-10-01 08:50:52.891249 | 2021-10-01 08:50:52.891249 |
|  5 |       5 |   5 | piyo5 | 2021-10-01 08:50:52.898149 | 2021-10-01 08:50:52.898149 |
|  7 |       7 |   7 | piyo7 | 2021-10-01 08:50:52.904952 | 2021-10-01 08:50:52.904952 |
+----+---------+-----+-------+----------------------------+----------------------------+
4 rows in set (0.00 sec)
```

# ロックの種類

MySQLには"行レベルロック"と"テーブルレベルロック"が存在します。
名前から推測できる通り、"行レベルロック"とは行単位でロックし、"テーブルレベルロック"はテーブル全体をロックします。
詳細は下記ドキュメントをご覧ください。

https://dev.mysql.com/doc/refman/8.0/ja/internal-locking.html

# ロックを確認する方法

別途公開している「ロックを確認するためのテーブルや設定」の記事をご確認ください。

https://qiita.com/ham0215/items/89fc277850d32f5b17cd

# 行レベルロックを検証する

この記事では行レベルロックを検証します。
下記のドキュメントに記載されています。

https://dev.mysql.com/doc/refman/8.0/ja/innodb-locking.html

行レベルロックは検証すべき点が多いので3つに分割して検証します。

* 共有ロック(S) / 排他ロック(X)
* インテンションロック
* レコードロック / ギャップロック / ネクストキーロック / 他

この記事では「インテンションロック」を検証します。

## インテンションロック

ここまでで　共有ロックと排他ロックの挙動がわかってきたので、ドキュメントを読み進めます。
ドキュメントを読み進めると「インテンションロック」という新たなロックが出てきます。

> InnoDB では、行ロックとテーブルロックの共存を許可する複数粒度ロックがサポートされています。 たとえば、LOCK TABLES ... WRITE などのステートメントは、指定されたテーブルに対して排他ロック (X ロック) を取得します。 複数の粒度レベルでロックするには、InnoDB で intention locks を使用します。 

インテンションロックは行ロックとテーブルロックの共存をサポートするために使われるロックのようです。
例えば『テーブルレベルロックで排他ロックが取得されている場合、そのテーブルの行に対して行レベルロックを行うことはできない』を実現するとき、テーブルレベルロックと行レベルロックは粒度が違うためロック可否を判定できません。
行ロックのときにテーブルレベルロックを事前に行うことで[テーブルレベルロックの記事](https://qiita.com/ham0215/items/f53dda06dd43e6be12c9)で説明したルールに則ってロック可否を判定することができるようになります。

>  インテントロックは、トランザクションが後でテーブルの行に必要とするロックのタイプ (共有または排他) を示すテーブルレベルのロックです。 インテントロックには、次の 2 種類があります:
・ intention shared lock (IS) は、トランザクションがテーブルの個々の行に shared ロックを設定することを示します。
・ intention exclusive lock (IX) は、トランザクションがテーブル内の個々の行に排他ロックを設定することを示します。
たとえば、SELECT ... FOR SHARE は IS ロックを設定し、SELECT ... FOR UPDATE は IX ロックを設定します。
インテンションロックの手順は次のとおりです。
・ トランザクションは、テーブル内の行に対する共有ロックを取得する前に、まず IS ロックを取得するか、テーブルに対して強いロックを取得する必要があります。
・ トランザクションは、テーブル内の行に対する排他ロックを取得する前に、まずテーブルに対する IX ロックを取得する必要があります。

共有ロックを取得するテーブルに対してISが取得され、排他ロックを取得するテーブルに対してIXが取得されるようです。
動作検証してみます。

```
mysql> begin;
Query OK, 0 rows affected (0.00 sec)


mysql> select * from piyos where id >= 3 and id <= 5 for share;
+----+---------+-----+-------+----------------------------+----------------------------+
| id | idx_num | num | name  | created_at                 | updated_at                 |
+----+---------+-----+-------+----------------------------+----------------------------+
|  3 |       3 |   3 | piyo3 | 2021-10-01 08:52:04.151293 | 2021-10-01 08:52:04.151293 |
|  5 |       5 |   5 | piyo5 | 2021-10-01 08:52:04.160298 | 2021-10-01 08:52:04.160298 |
+----+---------+-----+-------+----------------------------+----------------------------+
2 rows in set (0.00 sec)

mysql> select OBJECT_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA from performance_schema.data_locks;
+-------------+-----------+---------------+-------------+-----------+
| OBJECT_NAME | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA |
+-------------+-----------+---------------+-------------+-----------+
| piyos       | TABLE     | IS            | GRANTED     | NULL      |
| piyos       | RECORD    | S,REC_NOT_GAP | GRANTED     | 3         |
| piyos       | RECORD    | S             | GRANTED     | 5         |
+-------------+-----------+---------------+-------------+-----------+
3 rows in set (0.01 sec)
```

```
mysql> begin;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from piyos where id >= 3 and id <= 5 for update;
+----+---------+-----+-------+----------------------------+----------------------------+
| id | idx_num | num | name  | created_at                 | updated_at                 |
+----+---------+-----+-------+----------------------------+----------------------------+
|  3 |       3 |   3 | piyo3 | 2021-10-01 08:52:04.151293 | 2021-10-01 08:52:04.151293 |
|  5 |       5 |   5 | piyo5 | 2021-10-01 08:52:04.160298 | 2021-10-01 08:52:04.160298 |
+----+---------+-----+-------+----------------------------+----------------------------+
2 rows in set (0.00 sec)

mysql> select OBJECT_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA from performance_schema.data_locks;
+-------------+-----------+---------------+-------------+-----------+
| OBJECT_NAME | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA |
+-------------+-----------+---------------+-------------+-----------+
| piyos       | TABLE     | IX            | GRANTED     | NULL      |
| piyos       | RECORD    | X,REC_NOT_GAP | GRANTED     | 3         |
| piyos       | RECORD    | X             | GRANTED     | 5         |
+-------------+-----------+---------------+-------------+-----------+
3 rows in set (0.00 sec)
```

説明の通り共有ロックの時はIS、排他ロックの時はIXが取得されています。
LOCK_TYPE=TABLEとなっているので、テーブル単位のロックしていることもわかります。

> 次のマトリックスに、テーブルレベルのロックタイプの互換性の概要を示します。

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

テーブルレベルロックの関係がマトリクスで明示されていました。
ドキュメントに明記されている箇所を見つけられなかったのですが、SはREADロック、XはWRITEロックのことを指しているのだと思います。

これを見ると、IXはXとSの両方を禁止します。
ISはXは禁止しますが、Sは許容しています。

> 意図的ロックでは、完全なテーブルリクエスト (LOCK TABLES ... WRITE など) 以外はブロックされません。 意図的ロックの主な目的は、誰かが行をロックしていること、またはテーブル内の行をロックしていることを示すことです。

また、ここに書かれている通り、IXとISの組み合わせではロックは発生しません。
IXやISの組み合わせということは行レベルロックを行なっているということなので、テーブルレベルでは判定はせずに行レベルロックのルールに則って判断すればよいためこのような仕様になっているのだと思います。

こちらも検証しておきます。

### X → ?
最初にXを取得します。

```
mysql>  LOCK TABLES piyos WRITE;
Query OK, 0 rows affected (0.01 sec)

mysql> select OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, OWNER_THREAD_ID, OWNER_EVENT_ID from performance_schema.metadata_locks where OBJECT_SCHEMA = 'app_development' AND OBJECT_TYPE='TABLE';
+-------------+----------------------+-------------+-----------------+----------------+
| OBJECT_NAME | LOCK_TYPE            | LOCK_STATUS | OWNER_THREAD_ID | OWNER_EVENT_ID |
+-------------+----------------------+-------------+-----------------+----------------+
| piyos       | SHARED_NO_READ_WRITE | GRANTED     |              52 |             25 |
+-------------+----------------------+-------------+-----------------+----------------+
1 row in set (0.01 sec)
```

別のセッションでIX/ISを取得します。XやSの挙動は[テーブルロックレベルの記事](https://qiita.com/ham0215/items/f53dda06dd43e6be12c9)で検証済みなので省略します。

```
mysql> select * from piyos where id >= 3 and id <= 5 for share;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

mysql> select * from piyos where id >= 3 and id <= 5 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
```

両方、取得できませんでした。

### IX → ?
最初にIXを取得します。
IXはdata_locksやmetadata_locksで確認できます。

```
mysql> begin;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from piyos where id >= 3 and id <= 5 for update;
+----+---------+-----+-------+----------------------------+----------------------------+
| id | idx_num | num | name  | created_at                 | updated_at                 |
+----+---------+-----+-------+----------------------------+----------------------------+
|  3 |       3 |   3 | piyo3 | 2021-10-01 08:52:04.151293 | 2021-10-01 08:52:04.151293 |
|  5 |       5 |   5 | piyo5 | 2021-10-01 08:52:04.160298 | 2021-10-01 08:52:04.160298 |
+----+---------+-----+-------+----------------------------+----------------------------+
2 rows in set (0.00 sec)

mysql> select OBJECT_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA from performance_schema.data_locks;
+-------------+-----------+---------------+-------------+-----------+
| OBJECT_NAME | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA |
+-------------+-----------+---------------+-------------+-----------+
| piyos       | TABLE     | IX            | GRANTED     | NULL      |
| piyos       | RECORD    | X,REC_NOT_GAP | GRANTED     | 3         |
| piyos       | RECORD    | X             | GRANTED     | 5         |
+-------------+-----------+---------------+-------------+-----------+
3 rows in set (0.00 sec)

mysql> select OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, OWNER_THREAD_ID, OWNER_EVENT_ID from performance_schema.metadata_locks where OBJECT_SCHEMA = 'app_development' AND OBJECT_TYPE='TABLE';
+-------------+--------------+-------------+-----------------+----------------+
| OBJECT_NAME | LOCK_TYPE    | LOCK_STATUS | OWNER_THREAD_ID | OWNER_EVENT_ID |
+-------------+--------------+-------------+-----------------+----------------+
| piyos       | SHARED_WRITE | GRANTED     |              51 |             57 |
+-------------+--------------+-------------+-----------------+----------------+
1 row in set (0.00 sec)
```

別のセッションでX/S/ISを取得します。ISを取得する時に対象レコードがかぶると、行レベルで競合してしまうので対象レコードが被らないようにします。

#### IX → X, S

```
mysql>  LOCK TABLES piyos WRITE;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

mysql>  LOCK TABLES piyos READ;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
```

#### IX → IX

```
mysql> begin;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from piyos where id = 1 for update;
+----+---------+-----+-------+----------------------------+----------------------------+
| id | idx_num | num | name  | created_at                 | updated_at                 |
+----+---------+-----+-------+----------------------------+----------------------------+
|  1 |       1 |   1 | piyo1 | 2021-10-01 08:52:04.143776 | 2021-10-01 08:52:04.143776 |
+----+---------+-----+-------+----------------------------+----------------------------+
1 row in set (0.00 sec)

mysql> select OBJECT_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA from performance_schema.data_locks;
+-------------+-----------+---------------+-------------+-----------+
| OBJECT_NAME | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA |
+-------------+-----------+---------------+-------------+-----------+
| piyos       | TABLE     | IX            | GRANTED     | NULL      |
| piyos       | RECORD    | X,REC_NOT_GAP | GRANTED     | 1         |
| piyos       | TABLE     | IX            | GRANTED     | NULL      |
| piyos       | RECORD    | X,REC_NOT_GAP | GRANTED     | 3         |
| piyos       | RECORD    | X             | GRANTED     | 5         |
+-------------+-----------+---------------+-------------+-----------+
5 rows in set (0.00 sec)

mysql> select OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, OWNER_THREAD_ID, OWNER_EVENT_ID from performance_schema.metadata_locks where OBJECT_SCHEMA = 'app_development' AND OBJECT_TYPE='TABLE';
+-------------+--------------+-------------+-----------------+----------------+
| OBJECT_NAME | LOCK_TYPE    | LOCK_STATUS | OWNER_THREAD_ID | OWNER_EVENT_ID |
+-------------+--------------+-------------+-----------------+----------------+
| piyos       | SHARED_WRITE | GRANTED     |              51 |             81 |
| piyos       | SHARED_WRITE | GRANTED     |              52 |             87 |
+-------------+--------------+-------------+-----------------+----------------+
2 rows in set (0.00 sec)
```

#### IX → IS

```
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from piyos where id = 1 for share;
+----+---------+-----+-------+----------------------------+----------------------------+
| id | idx_num | num | name  | created_at                 | updated_at                 |
+----+---------+-----+-------+----------------------------+----------------------------+
|  1 |       1 |   1 | piyo1 | 2021-10-01 08:52:04.143776 | 2021-10-01 08:52:04.143776 |
+----+---------+-----+-------+----------------------------+----------------------------+
1 row in set (0.00 sec)

mysql> select OBJECT_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA from performance_schema.data_locks;
+-------------+-----------+---------------+-------------+-----------+
| OBJECT_NAME | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA |
+-------------+-----------+---------------+-------------+-----------+
| piyos       | TABLE     | IX            | GRANTED     | NULL      |
| piyos       | RECORD    | X,REC_NOT_GAP | GRANTED     | 3         |
| piyos       | RECORD    | X             | GRANTED     | 5         |
| piyos       | TABLE     | IS            | GRANTED     | NULL      |
| piyos       | RECORD    | S,REC_NOT_GAP | GRANTED     | 1         |
+-------------+-----------+---------------+-------------+-----------+
5 rows in set (0.00 sec)

mysql> select OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, OWNER_THREAD_ID, OWNER_EVENT_ID from performance_schema.metadata_locks where OBJECT_SCHEMA = 'app_development' AND OBJECT_TYPE='TABLE';
+-------------+--------------+-------------+-----------------+----------------+
| OBJECT_NAME | LOCK_TYPE    | LOCK_STATUS | OWNER_THREAD_ID | OWNER_EVENT_ID |
+-------------+--------------+-------------+-----------------+----------------+
| piyos       | SHARED_WRITE | GRANTED     |              51 |             81 |
| piyos       | SHARED_READ  | GRANTED     |              52 |             93 |
+-------------+--------------+-------------+-----------------+----------------+
2 rows in set (0.00 sec)
```

X / Sは失敗し、IX / ISは成功しました。

### S → ?
最初にSを取得します。

```
mysql>  LOCK TABLES piyos READ;
Query OK, 0 rows affected (0.00 sec)

mysql> select OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, OWNER_THREAD_ID, OWNER_EVENT_ID from performance_schema.metadata_locks where OBJECT_SCHEMA = 'app_development' AND OBJECT_TYPE='TABLE';
+-------------+------------------+-------------+-----------------+----------------+
| OBJECT_NAME | LOCK_TYPE        | LOCK_STATUS | OWNER_THREAD_ID | OWNER_EVENT_ID |
+-------------+------------------+-------------+-----------------+----------------+
| piyos       | SHARED_READ_ONLY | GRANTED     |              52 |             58 |
+-------------+------------------+-------------+-----------------+----------------+
1 row in set (0.00 sec)
```

別のセッションでIX / ISを取得します。XやSの挙動は[テーブルロックレベルの記事](https://qiita.com/ham0215/items/f53dda06dd43e6be12c9)で検証済みなので省略します。

#### S → IX

```
mysql> select * from piyos where id >= 3 and id <= 5 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
```

#### S → IS

```
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from piyos where id >= 3 and id <= 5 for share;
+----+---------+-----+-------+----------------------------+----------------------------+
| id | idx_num | num | name  | created_at                 | updated_at                 |
+----+---------+-----+-------+----------------------------+----------------------------+
|  3 |       3 |   3 | piyo3 | 2021-10-01 08:52:04.151293 | 2021-10-01 08:52:04.151293 |
|  5 |       5 |   5 | piyo5 | 2021-10-01 08:52:04.160298 | 2021-10-01 08:52:04.160298 |
+----+---------+-----+-------+----------------------------+----------------------------+
2 rows in set (0.00 sec)

mysql> select OBJECT_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA from performance_schema.data_locks;
+-------------+-----------+---------------+-------------+-----------+
| OBJECT_NAME | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA |
+-------------+-----------+---------------+-------------+-----------+
| piyos       | TABLE     | IS            | GRANTED     | NULL      |
| piyos       | RECORD    | S,REC_NOT_GAP | GRANTED     | 3         |
| piyos       | RECORD    | S             | GRANTED     | 5         |
+-------------+-----------+---------------+-------------+-----------+
3 rows in set (0.01 sec)

mysql> select OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, OWNER_THREAD_ID, OWNER_EVENT_ID from performance_schema.metadata_locks where OBJECT_SCHEMA = 'app_development' AND OBJECT_TYPE='TABLE';
+-------------+------------------+-------------+-----------------+----------------+
| OBJECT_NAME | LOCK_TYPE        | LOCK_STATUS | OWNER_THREAD_ID | OWNER_EVENT_ID |
+-------------+------------------+-------------+-----------------+----------------+
| piyos       | SHARED_READ_ONLY | GRANTED     |              52 |             58 |
| piyos       | SHARED_READ      | GRANTED     |              51 |             69 |
+-------------+------------------+-------------+-----------------+----------------+
2 rows in set (0.00 sec)
```

IXは失敗しましたが、ISは取得することができました。

### IS → ?
最初にIXを取得します。
ISもdata_locksやmetadata_locksで確認できます。

```
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from piyos where id >= 3 and id <= 5 for share;
+----+---------+-----+-------+----------------------------+----------------------------+
| id | idx_num | num | name  | created_at                 | updated_at                 |
+----+---------+-----+-------+----------------------------+----------------------------+
|  3 |       3 |   3 | piyo3 | 2021-10-01 08:52:04.151293 | 2021-10-01 08:52:04.151293 |
|  5 |       5 |   5 | piyo5 | 2021-10-01 08:52:04.160298 | 2021-10-01 08:52:04.160298 |
+----+---------+-----+-------+----------------------------+----------------------------+
2 rows in set (0.00 sec)

mysql> select OBJECT_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA from performance_schema.data_locks;
+-------------+-----------+---------------+-------------+-----------+
| OBJECT_NAME | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA |
+-------------+-----------+---------------+-------------+-----------+
| piyos       | TABLE     | IS            | GRANTED     | NULL      |
| piyos       | RECORD    | S,REC_NOT_GAP | GRANTED     | 3         |
| piyos       | RECORD    | S             | GRANTED     | 5         |
+-------------+-----------+---------------+-------------+-----------+
3 rows in set (0.00 sec)

mysql> select OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, OWNER_THREAD_ID, OWNER_EVENT_ID from performance_schema.metadata_locks where OBJECT_SCHEMA = 'app_development' AND OBJECT_TYPE='TABLE';
+-------------+-------------+-------------+-----------------+----------------+
| OBJECT_NAME | LOCK_TYPE   | LOCK_STATUS | OWNER_THREAD_ID | OWNER_EVENT_ID |
+-------------+-------------+-------------+-----------------+----------------+
| piyos       | SHARED_READ | GRANTED     |              51 |             75 |
+-------------+-------------+-------------+-----------------+----------------+
1 row in set (0.00 sec)
```

別のセッションでX/IX/S/ISを取得します。IXやISを取得する時に対象レコードがかぶると、行レベルで競合してしまうので対象レコードが被らないようにします。

#### IS → X

```
mysql> LOCK TABLES piyos WRITE;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
```

#### IS → S

```
mysql> LOCK TABLES piyos READ;
Query OK, 0 rows affected (0.00 sec)

mysql> select OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, OWNER_THREAD_ID, OWNER_EVENT_ID from performance_schema.metadata_locks where OBJECT_SCHEMA = 'app_development' AND OBJECT_TYPE='TABLE';
+-------------+------------------+-------------+-----------------+----------------+
| OBJECT_NAME | LOCK_TYPE        | LOCK_STATUS | OWNER_THREAD_ID | OWNER_EVENT_ID |
+-------------+------------------+-------------+-----------------+----------------+
| piyos       | SHARED_READ      | GRANTED     |              51 |             75 |
| piyos       | SHARED_READ_ONLY | GRANTED     |              52 |             63 |
+-------------+------------------+-------------+-----------------+----------------+
2 rows in set (0.00 sec)
```

#### IS → IX

```
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from piyos where id = 1 for update;
+----+---------+-----+-------+----------------------------+----------------------------+
| id | idx_num | num | name  | created_at                 | updated_at                 |
+----+---------+-----+-------+----------------------------+----------------------------+
|  1 |       1 |   1 | piyo1 | 2021-10-01 08:52:04.143776 | 2021-10-01 08:52:04.143776 |
+----+---------+-----+-------+----------------------------+----------------------------+
1 row in set (0.00 sec)

mysql> select OBJECT_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA from performance_schema.data_locks;
+-------------+-----------+---------------+-------------+-----------+
| OBJECT_NAME | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA |
+-------------+-----------+---------------+-------------+-----------+
| piyos       | TABLE     | IX            | GRANTED     | NULL      |
| piyos       | RECORD    | X,REC_NOT_GAP | GRANTED     | 1         |
| piyos       | TABLE     | IS            | GRANTED     | NULL      |
| piyos       | RECORD    | S,REC_NOT_GAP | GRANTED     | 3         |
| piyos       | RECORD    | S             | GRANTED     | 5         |
+-------------+-----------+---------------+-------------+-----------+
5 rows in set (0.00 sec)

mysql> select OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, OWNER_THREAD_ID, OWNER_EVENT_ID from performance_schema.metadata_locks where OBJECT_SCHEMA = 'app_development' AND OBJECT_TYPE='TABLE';
+-------------+--------------+-------------+-----------------+----------------+
| OBJECT_NAME | LOCK_TYPE    | LOCK_STATUS | OWNER_THREAD_ID | OWNER_EVENT_ID |
+-------------+--------------+-------------+-----------------+----------------+
| piyos       | SHARED_READ  | GRANTED     |              51 |             75 |
| piyos       | SHARED_WRITE | GRANTED     |              52 |             71 |
+-------------+--------------+-------------+-----------------+----------------+
2 rows in set (0.00 sec)
```

#### IS → IS

```
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from piyos where id = 1 for share;
+----+---------+-----+-------+----------------------------+----------------------------+
| id | idx_num | num | name  | created_at                 | updated_at                 |
+----+---------+-----+-------+----------------------------+----------------------------+
|  1 |       1 |   1 | piyo1 | 2021-10-01 08:52:04.143776 | 2021-10-01 08:52:04.143776 |
+----+---------+-----+-------+----------------------------+----------------------------+
1 row in set (0.00 sec)

mysql> select OBJECT_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA from performance_schema.data_locks;
+-------------+-----------+---------------+-------------+-----------+
| OBJECT_NAME | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA |
+-------------+-----------+---------------+-------------+-----------+
| piyos       | TABLE     | IS            | GRANTED     | NULL      |
| piyos       | RECORD    | S,REC_NOT_GAP | GRANTED     | 3         |
| piyos       | RECORD    | S             | GRANTED     | 5         |
| piyos       | TABLE     | IS            | GRANTED     | NULL      |
| piyos       | RECORD    | S,REC_NOT_GAP | GRANTED     | 1         |
+-------------+-----------+---------------+-------------+-----------+
5 rows in set (0.00 sec)

mysql> select OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, OWNER_THREAD_ID, OWNER_EVENT_ID from performance_schema.metadata_locks where OBJECT_SCHEMA = 'app_development' AND OBJECT_TYPE='TABLE';
+-------------+-------------+-------------+-----------------+----------------+
| OBJECT_NAME | LOCK_TYPE   | LOCK_STATUS | OWNER_THREAD_ID | OWNER_EVENT_ID |
+-------------+-------------+-------------+-----------------+----------------+
| piyos       | SHARED_READ | GRANTED     |              51 |             75 |
| piyos       | SHARED_READ | GRANTED     |              52 |             80 |
+-------------+-------------+-------------+-----------------+----------------+
2 rows in set (0.00 sec)
```

X以外は取得することができました。

# 次回

行レベルロックのインテンションロックが確認できたので、次の記事では「行レベルロック: レコードロック / ギャップロック / ネクストキーロック / 他」を検証していきます。

https://qiita.com/ham0215/items/99679d499869365446ec