🎉
デッドロックの原因、発生ケース、対策
デッドロックとは
お互いがお互いのロックが解除されるのを待っている状態になり、処理が進まない状態のこと
デッドロックの原因
トランザクション = Tr、テーブル = Tb
- Tr①
- TbA → TbBの順番でロック
- Tr②
- TbB → TbAの順番でロック
Tr①はTbBのロックが解除されるのを待ち、Tr②はTbAのロックが解除されるのを待つ
ロックの範囲
- テーブルロック:テーブル全体をロック
- 行ロック:行のみをロック(同じテーブルの他の行はロックされない)
InnoDBの行レベルロックの種類
レコードロック
- インデックスレコードに対するロック
- インデックスの効いたカラムで絞り込んだ場合は、その行のみロックされる
- インデックスのないカラムで絞り込んだ場合は、条件に合致するレコードの件数問わず全ての行がロックされる
ギャップロック
- 各行の隙間(ギャップ)にもロックをする
- 既存の行でなくても、条件に一致するような行のINSERTはブロックされる
ネクストキーロック
- 条件に一致する行としない行の隙間(ギャップ)にロックを取得する
- 例)col1に1, 4, 7, 9, 14の行があり、更新条件がcol1 > 3の場合
- col1が4, 7, 9, 14の行とその隙間(ギャップ)にロックが取得される(ギャップロック)
- 加えて、colが1と4の間にもロックが取得される(ネクストキーロック)
デッドロックの発生ケース
UPDATE
- Tr①がTbAのインデックスがないカラムに条件を指定し更新
- インデックスがないためTbAは全行ロック
- Tr②がTbBのインデックスがないカラムに条件を指定し更新
- インデックスがないためTbBはを全行ロック
- Tr①がTbBの1行を更新
- Tr②がTbBの全行にロックを取得しているため、Tr①は待機状態
- Tr②がTbAの1行を更新
- Tr①がTbAの全行にロックを取得しているため、Tr②は待機状態
デッドロック発生!
INSERT
- Tr3がTbCにINSERTで行を挿入
- 挿入した行にロックを取得
- Tr4がTbDにINSERTで行を挿入
- 挿入した行にロックを取得
- Tr3でTbDの2で挿入した行を含めて更新
- 2で挿入した行にTr4がロックを取得しているため、Tr3は待機状態
- Tr4はTbCの1で挿入した行を含めて更新
- 1で挿入した行にTr3がロックを取得しているため、Tr4は待機状態
デッドロック発生!
外部キー
デッドロックの検出
デッドロック発生でロールバックした時のエラーメッセージ
Deadlock found when trying to get lock; try restarting transaction
デッドロックの対処
例:アプリケーション側でデッドロックが発生した場合にリトライを行う仕組みを作成
デッドロックの発生率を下げる方法
ロックの範囲を小さくする
- トランザクションサイズを小さくする
- 適切なインデックスを使用する
ロックの取得時間を短くする
- ロックの取得範囲が狭い場合も、そのロックの取得時間が長い場合は、デッドロックの原因となる可能性が高くなる
同じ順序でロックを取得する
以下の順序でロックを取得できればデッドロックは発生しない
- Tr①がTbAをロック
- Tr②がTbAをロック
- Tr①がTbBをロック
- Tr①がコミットし、1と3のロックを解放
- Tr②がTbをロック
- Tr②がコミットし、2と5のロックを解放
Discussion