MySQLのdata_locksのLOCK_MODEに現れる値について
MySQLの8.0以降ではロックの挙動を調べる時にはperformance_schema.data_locksテーブルを利用することができます。
また、デットロックの調査ではperformance_schema.data_lock_waitsテーブルにこのdata_locksテーブルとinformation_schema.INNODB_TRXテーブルをjoinして加工したsys.innodb_lock_waitsビューにもお世話になると思います。
これらを利用する際に、data_locksではLOCK_MODEというカラム、sys.innodb_lock_waitsではwaiting_lock_mode/blocking_lock_modeというカラムが出てきますが、これらの値の意味が初見では若干わかりにくいかなと思ったのでそれについての簡単な説明と、それらの値はどのような仕組みで出力されるているのかを書いた記事です。
単なるXやSはレコードロックではなくネクストキーロック
まず適当にテーブルとデータを用意します。
create table t1 (
id bigint,
primary key id (id)
);
insert into t1 (id) values (1);
insert into t1 (id) values (3);
insert into t1 (id) values (5);
insert into t1 (id) values (7);
insert into t1 (id) values (9);
insert into t1 (id) values (11);
insert into t1 (id) values (13);
レコードロック、ギャップロック、ネクストキーロックを発生させるために、primary indexに対して範囲選択で排他ロックを取得します。
mysql> begin;
Query OK, 0 rows affected (0.01 sec)
mysql> select * from t1 where id between 3 and 10 for update;
+----+
| id |
+----+
| 3 |
| 5 |
| 7 |
| 9 |
+----+
4 rows in set (0.01 sec)
この状態でdata_locksを見てみると以下のような情報が取得できます。
mysql> select OBJECT_NAME,INDEX_NAME,LOCK_TYPE,LOCK_MODE,LOCK_STATUS,LOCK_DATA from performance_schema.data_locks;
+-------------+------------+-----------+---------------+-------------+-----------+
| OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA |
+-------------+------------+-----------+---------------+-------------+-----------+
| t1 | NULL | TABLE | IX | GRANTED | NULL |
| t1 | PRIMARY | RECORD | X,REC_NOT_GAP | GRANTED | 3 |
| t1 | PRIMARY | RECORD | X | GRANTED | 5 |
| t1 | PRIMARY | RECORD | X | GRANTED | 7 |
| t1 | PRIMARY | RECORD | X | GRANTED | 9 |
| t1 | PRIMARY | RECORD | X,GAP | GRANTED | 11 |
+-------------+------------+-----------+---------------+-------------+-----------+
6 rows in set (0.01 sec)
LOCK_TYPEがRECORDの行について、LOCK_MODEの値をみるとX,REC_NOT_GAP
、X
、X,GAP
の3種類の値が表示されています。 これらは全てX
なので排他ロックですが、単にX
となっている行はレコードロックではなくネクストキーロックです。そしてX,REC_NOT_GAP
がレコードロックで、X,GAP
がギャップロックです。上記のように並べてみるとわかりやすいですが、この部分を読み間違えるとなぜデットロックが発生したのか分からなかったりするので注意が必要です。
また、上記の例ではfor update
のため排他ロック(X
)でしたが、for share
等の場合は共有ロック(S
)になります。この場合も同じようにS
はネクストキーロックで、S,REC_NOT_GAP
がレコードロック、S,GAP
がギャップロックです。
なお、ネクストキーロックやギャップロックが何かについては良質な解説記事がたくさんあるためここで触れることはしませんが、MySQLのロックについてに添付されているスライドがオススメです。先のLOCK_MODEの表示には出てきていませんが、MySQLの行ロックには挿入インテンションロックというものもあり、そちらについても言及されています。このスライドが書かれた当時はロックを確認するにはshow engine innodb status
から拾って読んだりする必要があり、それなりに手間でしたが、いまはdata_locksやmetadata_locks等で簡単に見やすい形式で取得できるので、これらを使いながらスライドにある諸々の挙動を確かめるとわかりやすいと思います。
※章題をもう少し厳密に書くと「LOCK_TYPE: RECORDのロックについて、LOCK_MODEが単なるXやSであるものはレコードロックではなくネクストキーロック」です。これだと長すぎるので省略しています。
data_locksのLOCK_MODEをソースコードで追ってみる
次に、より理解を深めるために、data_locksテーブルのLOCK_MODEはMySQL(InnoDB)の内部でどのようにして出力されているのかを見ていきます。この過程で、内部ではロックの種類がどのように表現されているかについても知ることができます。
table_data_locksクラスからlock_get_mode_strまで
まず、performance_schema.data_locksテーブルの実態はtable_data_locksクラスです。
このテーブルを参照する際に呼ばれるメソッドはtable_data_locks::read_row_valuesです。
このメソッドの中身をみていくと、LOCK_MODEはm_rowのm_lock_modeを見ていることがわかります。
m_rowはtable_data_locksクラスのプライベートメンバ変数で、型はrow_data_lockです。
m_rowのm_lock_modeを書き込んでいる場所を探すと、PFS_data_lock_container::add_lock_row内にそれっぽい処理が見つかります。
これを読んでいる場所を探すとInnodb_data_lock_iterator::scan_trxの中にそれっぽい箇所が見つかります。
また、引数のlock_mode_strはlock_get_mode_strにトランザクションに紐づいているlockを渡して生成されていることがわかります。
lock_get_mode_strの定義を探すとlock0lock.ccに見つかります。そして、ここで実際に表示される文字列が生成されています。
lock_tとtype_modeについて
lock_get_mode_strの処理を読む前に、準備としてlock_get_mode_strの引数の型であるlock_tについて少し見ていきます。まず、lock_tとは何かについてですが、これはMySQL(InnoDB)でのロックに関する情報を保持する構造体です。
どんなロックなのかについての情報がtype_modeにエンコードされています。
エンコードされている情報は、lock0types.hで定義されているlock_modeと、
lock0lock.hで定義されている以下の定数です。
これらを見るとtype_modeは以下のようなレイアウトになっていることがわかります。
bit範囲 | 分類 | 説明 |
---|---|---|
1~4bit | Basic lock modes | 排他ロックなのか共有ロックなのか等(lock_modeのどれかの値) |
5~8bit | Lock types | ロック対象がレコードなのかテーブルなのか(LOCK_TABLEもしくはLOCK_REC) |
9bit | Waiting lock flag | ロックを獲得済みかどうか |
10bit~ | Precise modes | より詳細なロックモード |
Basic Lock modes以外は単純な定数で定義されており、ビットフラグになっています。Lock typesはどちらかのフラグしか立ちませんが、Precise modesに関しては複数のフラグが立つことがあります(LOCK_GAPとLOCK_INSERT_INTENTION等)。
少し興味深いのは、Precise modesのLOCK_ORDINARYは0と定義されており、これはコメントにあるようにネクストキーロックであることを表すmodeです。つまり、Lock typesがLOCK_RECORDでPrecise modesのフラグが何も立っていないものがネクストキーロックです(判定ロジック)。
このことから、data_locksのLOCK_MODEではPrecise modesで定義されている各種フラグが立っている場合に表示が増え、単にX
やS
とだけ表示されているものはそれらフラグが一切立ってないことを示しており、それはつまりネクストキーロックであるということがなんとなくわかります。
lock_get_mode_strでの文字列生成
lock_tとtype_modeについて説明したので、lock_get_mode_strの処理を見ていきます。
まずこの部分ですが、type_modeのBasic lock modes、Lock types、Precise modesのそれぞれについて、他のbit範囲を0にしたものをmode、type、flagsに入れています。
次に、以下の部分でmodeに入っているlock_mode(enum)の値をlock_mode_stringに渡して文字列に変換した後に、先頭のLOCK_
を消して出力に渡しています。これは例えばX,REC_NOT_GAP
のX
の部分です。
最後に、以下の部分でflagsに入ってるPrecise modesの各種フラグをチェックして、フラグが立っていればフラグに対応する文字列をカンマ区切りで出力しています。これは例えば、X,GAP,INSERT_INTENTION
の,GAP,INSERT_INTENTION
の部分です。
以上がdata_locksのLOCK_MODEに値が表示されるまでです。
Discussion