ほげほげ見聞録

技術メモ、備忘録、使い方はそのうち覚える

MySQL(InnoDB)でデッドロックが発生したので調査

エラー発生

ある日、以下のエラーが発生した。どうやらデッドロックが起きたらしい。
「SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock; try restarting transaction with query:」

発生個所

一度登録内容を削除し、元の内容+新規の内容を登録するような処理をしていた場所でエラー発生。
つまり、DB登録処理のDELETE-INSERTのINSERT部分。

ログから分かったこと

  • 同時刻に二つ以上のクライアントが同じ処理を実行
  • 各クライアントは別のレコードを対象に操作
  • デッドロックが発生したのは一クライアントだけ

いわゆる「別のトランザクションから同一レコードを操作すると起きるデッドロック(レコードロック)」ではないと分かる。

とりあえず原因

結果が0件のレコードに対してDELETEすると、テーブルに対しロックが掛かってしまう。

解決方法

DELETE前に削除対象が0件でないことを確認する。削除対象がない場合はDELETEしない。

細かい原因

ネクスキーロック」なるものが発生していたとのこと。
- https://dev.mysql.com/doc/refman/5.6/ja/innodb-next-key-locking.html

自分はDB専門家ではないので、先人の詳しくて分かりやすい資料を見るのが一番。そのうちDBの深いところも勉強してみたい…。
- http://d.hatena.ne.jp/jflute/20120831/1346393321 - http://d.hatena.ne.jp/sh2/20140914 - https://www.slideshare.net/yuyamada777/delete-77702365


解決までの道中

以下は余談。
すぐに解決できたわけではなく、色々試行錯誤が多かった。
というのも、エラー発見時には「デッドロック」といえば「レコードロック」だという先入観があったからだ。

先人の知恵を借りる為、何はともあれググってみた。
エラーメッセージの検索結果では、レコードロックについてしか説明がされていない。
InnoDB ロック」など範囲を広げて検索してみると、レコード単位だけではなくテーブル単位でロックされる場合があると分かった。

結論

InnoDBにはデッドロックになる条件が沢山あった。
マニュアルはちゃんと読んでおかないとダメですね…。
- https://dev.mysql.com/doc/refman/5.6/ja/innodb-record-level-locks.html

「ギャップロック」なんて知らずにはまってしまいそうで怖い。
- https://qiita.com/kenjiszk/items/05f7f6e695b93570a9e1

このデッドロックの確認方法

エラーの確認は手元でやりたいもの。
みんな大好きJMeterを使うと、今回発生したデッドロックを再現できる。
要は、あるクライアントのDELETE-INSERT処理中に他のクライアントが同じ処理をすればいい。
JMeterで上をやる場合、スレッド数を2以上に設定で複数クライアントを再現する。
スレッド実行の間が空いてないとエラーログが上手く残らなかったりするので、必要に応じて設定するのがよさそうだった。