kawabatas技術ブログ

試してみたことを書いていきます

空のUPDATEによるDEADLOCK

概要

負荷検証中に deadlock のログを見つけて、その対処をしたのでメモです。

クエリ特定

SHOW ENGINE INNODB STATUS; コマンドを実行すると、

LATEST DETECTED DEADLOCK でどのクエリで deadlock が発生したのかわかります。

今回は

INSERT INTO tokens (id, token) VALUES(1, 'token');

INSERT INTO tokens (id, token) VALUES(2, 'token2');

のようなクエリで deadlock になっていることがわかりました。

原因&対処

INSERT の直前で、UPDATE tokens SET delete_time=NOW() WHERE id=1; のような UPDATE をしており、 これが空の場合、ネクスキーロックがかかってしまうためとわかりました。

参考

試す。 ターミナル1

mysql> BEGIN;
Query OK, 0 rows affected (0.01 sec)

mysql> UPDATE tokens SET delete_time=NOW() WHERE id=1;
Query OK, 0 rows affected (0.03 sec)
Rows matched: 0  Changed: 0  Warnings: 0

mysql> INSERT INTO tokens (id, token) VALUES(1, 'token');
Query OK, 1 row affected (7.07 sec)

ターミナル2

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> UPDATE tokens SET delete_time=NOW() WHERE id=2;
Query OK, 0 rows affected (0.01 sec)
Rows matched: 0  Changed: 0  Warnings: 0

mysql> INSERT INTO tokens (id, token) VALUES(2, 'token2');
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

Deadlock!!!

対処。

アプリケーションのロジックで、まず、SELECT で存在をチェックして、存在すれば UPDATE する形にしました。

// ロックを避けるために存在チェック
isExist, err := // SELECT * FROM tokens WHERE id=?;
if err != nil {
    return nil, err
}
if isExist {
    // UPDATE
}

// INSERT

XXXロックとかの理解を深めないと。。。

あとで読もう

MySQL デッドロック回避パターン - あっさりと

MySQLのデッドロック対処 おまけでギャップロック│システムガーディアン株式会社