ツリー構造から見るMySQLのインテンションロック

ストレージエンジンは InnoDB. MySQL バージョンは 8.0.33

# 目次

* 前置き - ロックの粒度
  * A. データベースロック
  * B. テーブルロック
  * C. ページロック
  * D. 行ロック
* 本題 - インテンションロック
* 想定外ロックを避けるには

# 前置き - ロックの粒度

( この辺知ってる、という方は **[本題 - インテンションロック](https://qiita.com/ymatsukawa/items/ecbb2375667bedc16869#%E6%9C%AC%E9%A1%8C---%E3%82%A4%E3%83%B3%E3%83%86%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%AD%E3%83%83%E3%82%AF)** まで進んでください )

ロックの粒度は次の4つ

* A. データベースロック
* B. テーブルロック
* C. ページロック
* D. 行ロック

図にすると次の通り。

![‎lock_intention.‎001.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31223/9924aaf2-61bc-ddf4-96ad-a958ae797f23.jpeg)

## A. データベースロック

MySQL では明示的にデータベースロックする「コマンド」や「設定値」はない。

以下 2 つでそれに近い状態が出来上がる。

**疑似的なロック**

```sql
FLUSH TABLES WITH READ LOCK;
```

この SQL は以下の特性を持つ。

* テーブル全体に向けて
* 書込を禁止する
* 読取を許可する

```sql
UNLOCK TABLES;
```

で解除可能。

**長く続くトランザクション**

意図的でない限り、 一つのトランザクションが延々と続く場合は

他トランザクションをロックする可能性を高くする。

その意味で、データベースロックと似た状況が生じる。

* 例1.) 何千万、何億とある SQL を一つのトランザクションで処理する
* 例2.) インデックス設計が不適切で、何十分経っても結果を取得できない

## B. テーブルロック

**テーブル全体に対するロック**

例えば、テーブル内の全行を一括更新・削除する場合に発生する。

[参考ページ](https://qiita.com/ymatsukawa/items/562d002dcdd602f88a42#a2-%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%83%AD%E3%83%83%E3%82%AF)

## C. ページロック

ここでは省略。

「[述語ロック(Predicate Lock)](https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html#innodb-predicate-locks)」がこれに対応するが、他のロックに比べれば発生頻度(利用頻度)は低いと思われるため。

**補足：ページとは**

データを保存・管理する基本単位のことで「コンテナ」がイメージとして近い。

以下 4 種類のデータを格納する。

* テーブルの行データ
* インデックスデータ
* トランザクション情報
* (ページそのものの)メタデータ

[参考ページ](https://blog.jcole.us/2013/01/03/the-basics-of-innodb-space-file-layout/)

## D. 行ロック

**行に対するロック**

ロックの範囲が行単位である。

[参考ページ](https://qiita.com/ymatsukawa/items/562d002dcdd602f88a42#a1-%E8%A1%8C%E3%83%AD%E3%83%83%E3%82%AF)

## 本題 - インテンションロック

インテンションロックとは、

* トランザクションがテーブルに対して取得するロックのこと
* **B. のテーブルロックとは別物**

図解すると以下の通り

### その1 - 最初にインテンションロックを確認する

* トランザクションは、テーブルに「インテンションロック」があるか確認する

![‎lock_intention.‎002.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31223/53ba4f50-1fe4-6d53-a4da-74527a0a5305.jpeg)

### その2 - テーブルにインテンションロック・行にロックを取得

確認の結果、テーブルにインテンションロックがないので

* テーブルにインテンションロックを取得
* 行にロックを取得
  * **トランザクションが続く限り、上 2 つのロックは継続する**

![‎lock_intention.‎003.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31223/e6d2d75e-8fe1-c9f3-805c-4f8bf2a0a240.jpeg)

### その３ - 別トランザクションがインテンションロックを確認

別トランザクションが、テーブルのインテンションロックを確認する

* 行ロック可能かを **テーブルのインテンションロックから**確認する
* (できないので)先約のトランザクション完了待ち
  * いわゆるロック状態・ロック解放待ち

できる・できないの突合は[ドキュメントのマトリクスを参照](https://dev.mysql.com/doc/refman/8.0/ja/innodb-locking.html#innodb-intention-locks)

![‎lock_intention.‎004.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31223/52a1ca70-de5a-d57b-c0b1-f3d456f518c8.jpeg)

### その4 - トランザクション再開後、先にインテンションロックする

先約トランザクション完了 = ロック解放されたら、次のトランザクション用に

* テーブルにインテンションロックを取得
* 行にロックを取得

![‎lock_intention.‎005.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31223/7862ed14-ccc6-c491-28ad-90cf3b685097.jpeg)

### なぜ、インテンションロックが必要か

処理効率化のため。

例えば、テーブル T1 の行数(レコード数) が 5,000万あると仮定する。

インテンションロックがない場合、インデックスが適切でない SQL 等は

「全行の検査」が必要。逐次処理では非常に無駄なため、

インテンションロックが設けられている。

# 想定外ロックを避けるには

* 長いトランザクション
* インテンションロック

が組み合わさると、予期しないロックが発生する。なので、次に留意したい

* 特に理由がない限り、トランザクションは短くすること
  * 取得と更新のトランザクションは分けるなど
* インデックス設計を見直して、高速化を図ること
* 性能テスト・負荷テストをして以下を確認すること
  * 長いトランザクションが発生しないか
  * 競合しあうトランザクションは発生しないか

# 参考

* [MySQL Concurrency](https://www.oreilly.com/library/view/mysql-concurrency-locking/9781484266526/)
* https://www.youtube.com/watch?v=niUep0Em6pk