MySQL (InnoDB) デッドロックの発生原因とSHOW ENGINE INNODB STATUSによる解析
MySQL Database Performance SQL
結論
SHOW ENGINE INNODB STATUS で競合箇所を特定し、アプリ側で Error 1213 の自動再試行を実装します。
-- MySQL公式仕様:最新のデッドロックログを出力して原因解析
SHOW ENGINE INNODB STATUS\G
MySQL (InnoDB) 公式仕様:デッドロック処理メカニズム
MySQL公式ドキュメント(dev.mysql.com/doc/refman/8.0/en/innodb-deadlock-detection.html)の規定通り、InnoDB ストレージエンジンは複数トランザクション間でロック依存関係の循環(デッドロック)を常時監視しています。
- 自動検出(Deadlock Detection): 循環待ちを検知すると、InnoDB は影響行数の少ないトランザクション(Victim)を即座に選定して強制ロールバック(Error 1213)させます
- タイムアウト退避 (
innodb_lock_wait_timeout): 検知機能がOFFの場合はinnodb_lock_wait_timeout(デフォルト50秒)経過後に Error 1205 でタイムアウト処理されます
実際に起こる障害:Error 1213 による決済・データ欠損障害
データベース側のデッドロック検出は正常な自己防衛機能ですが、アプリケーション側で Error 1213 をハンドリングしていない場合、ユーザーへ 500 エラーが突然返り、注文データや決済処理が途中で消失する障害 が発生します。
RDBMSではデッドロックの完全ゼロ化は不可能なため、アプリ側での自動再試行(Retry)設計が前提となります。
対策手順と実装
SHOW ENGINE INNODB STATUS\Gを実行し、LATEST DETECTED DEADLOCKセクションのロック保持SQLと要求SQLを確認する- アプリケーション全体で、複数テーブルを更新する際のクエリ順番(例: 必ず
usersテーブル更新 →ordersテーブル更新の順)を統一する - アプリのDBアクセス層で
1213(Deadlock) をキャッチし、指数バックオフで数回再試行するロジックを実装する
// ⭕ Node.js (TypeScript) でのデッドロック自動再試行実装パターン
async function executeWithRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (err: any) {
// MySQL Error 1213: ER_LOCK_DEADLOCK
if (err.errno === 1213 && attempt < maxRetries) {
console.warn(`デッドロック検知。再試行中 (${attempt}/${maxRetries})...`);
await new Promise(res => setTimeout(res, attempt * 100)); // リトライ
continue;
}
throw err;
}
}
throw new Error('再試行上限に達しました');
}