20年越しに修正されたMySQL Bug #11472 ── 外部キーCASCADEでトリガーが起動しなかった理由と影響を解説

はじめに

2005年6月21日に報告され、19年9か月も放置されていたMySQLの著名バグ Bug #11472 が、ついに修正された。Reddit r/programming で 1500 票以上を集めた話題のバグは、「親テーブルを ON DELETE CASCADE で削除したとき、連鎖的に更新される子テーブルのトリガーが起動しない」という、一見地味だが運用上は致命的な問題だ。本記事では、なぜこのバグが20年も生き残ってしまったのか、InnoDBの内部構造の何が原因だったのか、そしてMySQL 9.7で挙動が変わる今、エンジニアが何を確認しておくべきかを掘り下げる。

バグの正体 ── 「静かに失敗する」トリガー

Bug #11472 を一言で言えば「親テーブルを削除/更新したとき、子テーブルのトリガーが起動しない」というものだ。報告者の Omer Barnir 氏が示した再現コードはシンプルで、t1 から1行 DELETE すると ON DELETE SET NULL で t2 の5行が UPDATE されるが、t2 に仕込んだ AFTER UPDATE トリガーがカウントするはずの @counter が 0 のまま、というものだった。

問題は「失敗」が静かなことだ。エラーは出ない。DELETE も UPDATE も成功し、データも整合している。ただトリガーが「呼ばれなかった」だけ。監査ログ、派生データの更新、イベント駆動の通知、updated_at カラムの自動更新 ―― これらをトリガーで実装している環境では、正常系のテストを通っているのに本番で監査記録が抜けるという事態が長年発生していた。

報告から2年後の2007年には InnoDB 開発者の Heikki Tuuri 氏が「修正にはまだ時間がかかる」とコメントし、2009年には Konstantin Osipov 氏が「5.1 では直さない、実験的な 6.1-fk ブランチを見てくれ」と返答。その後も「Verified(確認済み)」のステータスのまま動かず、コミュニティでは「20周年に向かう伝説のバグ」として半ば名物扱いされてきた。

なぜ20年直せなかったのか ── InnoDBとSQL層の境界

このバグの根本原因は、MySQLが歴史的に 「外部キー制約の実装場所」と「トリガーの実装場所」を分けてしまった ことにある。

InnoDB は外部キーCASCADE 処理を、SQL層を経由せずに ストレージエンジン内部で完結 する形で実装してきた。親行の削除が走ると、InnoDB は B-Tree インデックスを直接たどって子行を見つけ、ストレージレイヤーで直接 UPDATE や DELETE 相当の処理を実行する。性能上は理にかなった設計だが、副作用として SQL層が管理するトリガー機構を完全にバイパスしてしまう。トリガーは「SQL文が実行された」というイベントに紐づいて起動するため、SQL文を経由しないカスケードでは起動しようがなかったのだ。

この設計は単にコードを書き換えればいい話ではない。トリガーをカスケードで発火させるには、ストレージエンジンからSQL層へのコールバック経路を作り、加えて トリガー実行中にさらに別のカスケードが連鎖した場合の挙動 や、BEFORE トリガーが行を書き換えた場合の外部キー整合性 など、SQL標準とInnoDBの整合性ルールの間で詰めるべき意思決定が山積みだった。「直せる人が腰を据えて取り組むには、優先度の高い案件が常に他にあった」のが、20年放置された実態だろう。

ちなみに PostgreSQL は最初からこの問題を持っていない。Postgres の外部キーCASCADE は内部的に「システムトリガー」として実装されているため、ユーザー定義のトリガーも自然に連鎖して発火する。Postgres 公式は「ユーザーBEFOREトリガーがカスケード中に行を書き換えるのは仕様」とまで明言している。アーキテクチャの違いが、20年の差を生んだ。

修正の中身 ── MySQL 9.6 のSQL層FKハンドリングと WL#17024

2025年3月17日、MySQL チームは WL#17024「Activate triggers on referencing tables during foreign key CASCADE」 として、ついにこのバグを修正した。修正がここまで遅れた裏には、前段として MySQL 9.6 で導入された SQL-layer foreign key handling がある。

9.6 では innodb_native_foreign_keys というシステム変数が追加され、これを OFF にすることで外部キー制約の解決をInnoDBではなく SQL層側で行う 経路が開かれた。SQL層で処理されるということは、カスケードによる子行の UPDATE/DELETE が通常のSQL文と同じパスを通るということで、トリガーの発火点を素直に挿し込めるようになる。WL#17024 はこのSQL層経路上に、子テーブルの BEFORE/AFTER 両方のトリガー起動ロジックを実装した。

挙動の対応は以下のとおりだ。

  • ON DELETE CASCADE → 子テーブルの BEFORE DELETE / AFTER DELETE トリガーが発火
  • ON UPDATE CASCADE → 子テーブルの BEFORE UPDATE / AFTER UPDATE トリガーが発火
  • ON DELETE SET NULL / ON UPDATE SET NULL → カラムが NULL に書き換わるので UPDATE トリガー が発火

修正は MySQL 9.7 で正式リリースされる。注意点として、この機能は innodb_native_foreign_keys = OFF を前提とする。デフォルト設定ではInnoDB側でCASCADE処理が完結したままなので、トリガーは引き続き起動しない。innodb_native_foreign_keys のデフォルト値と切替手順、性能インパクトについては9.7 GA時のリリースノートを必ず確認したい。

エンジニアへの影響 ── 「直って嬉しい」だけでは済まない

このバグの修正は、嬉しいニュースであると同時に 既存システムに対する破壊的変更 でもある。20年「動かなかった」挙動が「動く」ようになるということは、その挙動に依存していなかったコードが急に動き出すということだ。

実務でチェックすべきポイントは3つある。

1. カスケード経路にあるトリガーを棚卸しする。外部キーで参照される子テーブルに AFTER UPDATE や AFTER DELETE のトリガーが定義されている場合、これまで起動しなかったロジックが9.7以降は起動する。監査ログを二重に書く、updated_at を上書きするなどの副作用が新たに発生していないか、ステージングで実データに近い負荷をかけて確認する必要がある。

2. アプリ側で実装していた「カスケード代替コード」を見直す。バグ #11472 を回避するため、多くのチームはアプリケーション層で「親を削除する前に子を明示的に削除し、トリガーを発火させる」「ストアドプロシージャでカスケードを再実装する」といった工夫をしてきた。9.7に移行する際は、これらの代替コードがDBレベルのトリガー発火と二重に動くリスクがある。

3. innodb_native_foreign_keys の方針を決める。SQL層FKハンドリングはトリガー起動以外にも、外部キー制約の挙動全般がInnoDB実装からSQL層実装に切り替わるため、パフォーマンス特性やロック挙動が変わる可能性が高い。すべての本番DBで一斉に OFF に倒すのではなく、影響の小さい環境から段階的に検証していく運用が現実的だ。

まとめ

MySQL Bug #11472 は、外部キーCASCADEがInnoDBストレージ層で完結し、SQL層のトリガー機構をバイパスしていたために20年生き残った。MySQL 9.6 でSQL層FKハンドリングの土台ができ、9.7 / WL#17024 でようやくトリガー発火が実装された。修正は歓迎すべきだが、デフォルトでは有効化されない点と、既存のアプリケーション側カスケード代替コードとの衝突に注意が必要だ。Postgres ではあたりまえに動いていた挙動が、MySQLでもようやく追いついた ―― このタイミングで、自分のスキーマの外部キーとトリガーの設計を棚卸ししておく価値は十分にある。

今日のその他のニュース

Using AI to write better code more slowly(HackerNews 1071pts)

「AIで速くコードを書く」ではなく「AIで丁寧に良いコードを書く」というカウンター論考が大きな支持を集めた。生産性議論に偏りがちなAIコーディング言説に、品質側からの強い反論を投げかける。(記事)

GitHub Actions: persist-credentials: false を設定するべき(Zenn 103likes)

actions/checkout のデフォルト挙動で credential が GITHUB_TOKEN として残り続け、サプライチェーン攻撃の起点になり得る指摘。persist-credentials: false を明示する運用が推奨される。(記事)

Apple Memory Integrity Enforcement、初の研究者バイパス

Apple が大々的に打ち出した Memory Integrity Enforcement(MIE)に対し、研究者による最初のバイパスが公開された。iOS/macOS のメモリ保護機構の限界を考えるうえで重要な事例。(記事)

ソース