MySQLにおけるデッドロックの調査方法
デッドロックの概要
デッドロックとは、それぞれが他の必要なロックを保持しているために、異なるトランザクションを続行できない状況です。 両方のトランザクションがリソースが使用可能になるのを待機しているため、保持しているロックは解放されません。
- 互いにロックの解放待ちを行う場合に発生する。
- デッドロックが発生するのは、ロックの待機を実施する排他ロックが1つでも含まれる場合
共有ロックと排他ロック
- 共有ロック = sロック:読み取りを行なっているため、他からの更新を制限するロック
- 排他ロック = xロック:更新中のため、他からの更新を制限するロック
共有ロックは複数のトランザクションが取得できるが、排他ロックは単一のトランザクションしか取得できない。
共有ロックと排他ロックによるデッドロック
次のようなケースで発生する。
- トランザクションAが共有ロック取得
- トランザクションBが共有ロック取得
- トランザクションAが排他ロック取得(待機)
- トランザクションBが排他ロック取得(待機)
この時点でデッドロックが発生し、トランザクションBがロールバックされる(必ずトランザクションBなのか自信がない)
排他ロック同士のデッドロック
相手がロックを持っているリソースに対してそれぞれ排他ロックを取得すると、デッドロックが発生する。
ロックの範囲
いくつかあるみたいだけど、基本的にはテーブルロックと行ロック
インテンションロック
インテントロックは、トランザクションが後でテーブルの行に必要とするロックのタイプ (共有または排他) を示すテーブルレベルのロックです。 インテントロックには、次の 2 種類があります:
intention shared lock (IS) は、トランザクションがテーブルの個々の行に shared ロックを設定することを示します。
intention exclusive lock (IX) は、トランザクションがテーブル内の個々の行に排他ロックを設定することを示します。
intent の意味の通りで、意図を示すためのロック。
SELECT ... FOR SHAREはISロックを、SELECT ... FOR UPDATEはIXロックを取得する。
意図的ロックでは、完全なテーブルリクエスト (LOCK TABLES ... WRITE など) 以外はブロックされません。 意図的ロックの主な目的は、誰かが行をロックしていること、またはテーブル内の行をロックしていることを示すことです。
あ〜、テーブルロックと行ロックで粒度が異なるけど、行ロックであっても「このテーブルに対してロックがかかっているらしい」という意図が伝わることで、テーブルロックが正しく動作できるようになるということか。
InnoDB におけるレコードロック
条件に合致した行に対するロックではなく、スキャンしたインデックスに対するロック。
レコードロックでは、テーブルにインデックスが定義されていなくても必ず、インデックスレコードがロックされます。 このような場合は、InnoDB によって非表示のクラスタ化されたインデックスが作成され、このインデックスを使用してレコードロックが行われます。
レポートでは次のように見えるそう。
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10078 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000274f; asc 'O;;
2: len 7; hex b60000019d0110; asc ;;
これがわかりやすかった
ギャップロック
複数の範囲にかかるロック。
明示的に WHERE句で指定するのはわかりやすいが、注意したいのは存在しないレコードへの更新時、インデックスのはざますべてに対してロックがかかること。