データモデル設計ガイド

外部キー制約の使いどころと ON DELETE オプション

整合性保証と ON DELETE CASCADE / SET NULL / RESTRICT の使い分け。

外部キー制約の使いどころと ON DELETE オプション diagram

外部キー制約が守るもの

外部キー (FOREIGN KEY) 制約は、「この列の値は必ず参照先テーブルの主キーに存在する」ことを DB レベルで保証します。アプリ側のバグでデタラメな user_id が入ってしまっても、DB が拒否します。

外部キーを張らない選択肢ももちろんあります(後述)が、そのときは「整合性はアプリで絶対に守る」という約束を永続的に引き受けることになります。開発者が変わってもテストが手薄でも守れるか、と問うと、たいていの場合は DB に任せた方が安全です。

sql
CREATE TABLE orders (
  id       BIGSERIAL PRIMARY KEY,
  user_id  BIGINT NOT NULL,
  total    NUMERIC(12,2) NOT NULL,
  FOREIGN KEY (user_id) REFERENCES users(id)
    ON DELETE RESTRICT
    ON UPDATE CASCADE
);

ON DELETE: CASCADE / SET NULL / RESTRICT / NO ACTION

親が削除されたときの子の扱いを ON DELETE 句で決めます。

  • RESTRICT / NO ACTION: 子がいる親の削除を拒否する(安全側。デフォルトは RDBMS により異なる)
  • CASCADE: 親削除時に子も連鎖削除。「親なしでは存在意義がない」子向け(注文と注文明細)
  • SET NULL: 子の外部キー列を NULL にする。「親とのつながりは任意」な関係向け(記事とカテゴリ)。列が NOT NULL だと使えない
  • SET DEFAULT: デフォルト値にセット。使用頻度は低い

RESTRICT と NO ACTION の違い: NO ACTION はトランザクション末尾に検査を遅延できる(DEFERRABLE 指定時)、RESTRICT は即座に検査。PostgreSQL ではこの差が出る場面があります。

CASCADE は便利だが過信は禁物

ON DELETE CASCADE は連鎖削除してくれて便利ですが、意図しない大量データ削除の入口にもなります。ユーザー 1 人を消したら、その人の注文・レビュー・コメント・ファイルが全部消える、というのが本当に望ましいのかを毎回確認します。

さらに、CASCADE 連鎖中は大量の行ロックが発生し、他トランザクションをブロックします。数百万行の子テーブルで CASCADE 削除すると、本番で長時間のロック待ちを引き起こすことがあります。

代替案: 論理削除(deleted_at)+ 定期バッチでの物理削除に切り替えると、ピーク時間帯のロックを回避できます。

パフォーマンス — インデックスと書き込みコスト

外部キー制約には隠れたコストがあります。

  • 親側の PK は必ずインデックス済みだが、子側の FK 列にはインデックスが自動生成されない(MySQL InnoDB は例外で自動生成)。親を削除・更新すると子テーブルをフルスキャンして参照を確認するので、必ず子側 FK 列にインデックスを張る
  • 書き込みのたびに参照先存在チェックが走るため、INSERT/UPDATE がわずかに遅くなる
  • バルクロード時は外部キー制約を一時的に無効化することもある(PostgreSQL: ALTER TABLE ... DISABLE TRIGGER ALL、MySQL: SET FOREIGN_KEY_CHECKS = 0

MySQL InnoDB 固有の注意点

MySQL InnoDB には外部キーまわりの独特な挙動があります。

  • FK 列に自動でインデックスを生成する(PostgreSQL / SQL Server はしない)
  • 文字列型 FK の場合、文字コード / 照合順序 (collation) が親子で一致しないと FK 作成が失敗する(utf8mb4_0900_ai_ci 等を親子で揃える。数値型 FK には関係しない)
  • ALTER TABLE ... DROP FOREIGN KEY で名前を指定する必要があり、SHOW CREATE TABLE で確認が必要
  • MyISAM エンジンは FK を無視する(現在は InnoDB 既定なので通常問題にならないが、古いスキーマで遭遇することがある)

外部キーを張らない選択肢

大規模・高スループットな系では、あえて外部キー制約を張らない設計もあります。

  • シャーディング / マイクロサービス境界: 参照先が別 DB / 別サービスにあると FK を張れない
  • イベントログ / 時系列テーブル: 取り込み速度最優先で、整合性は ETL 後にチェックする
  • 書き込みピーク緩和: FK チェックのオーバーヘッドを削って INSERT レートを稼ぐ

いずれの場合も、「整合性をどこで保証するか」を明文化することが必須です。暗黙のうちに FK を外すのが最悪のパターンです。