CASCADE vs RESTRICT vs SET NULL
CASCADE vs RESTRICT vs SET NULL とは
外部キー制約におけるCASCADE、RESTRICT、SET NULLの違いを解説します。本レッスンでは、CASCADE vs RESTRICT vs SET NULL の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。
なぜ重要か
前回は ON DELETE / ON UPDATE のポリシーが 5 つあることを学びました。実務では特に CASCADE / RESTRICT / SET NULL の 3 つを目にすることが多く、ここをきちんと使い分けられるかどうかで、データの安全性と運用負荷が大きく変わります。同じテーブルでも子供のような関係 (注文と注文明細) と、履歴のような関係 (顧客と注文) では選ぶべきポリシーが正反対になることもあります。
3 つを並べて比較
親が消えたときの子の運命がポリシーごとに違います。同じ親 -> 子の関係でも、ビジネス要件次第で選択は異なります。
具体的なシナリオ
たとえば次の 3 つを考えてみます。
SQL クエリ
-- 1. orders と order_items (注文と明細)
-- -> 明細は注文の付属物。注文が消えれば明細も消えるべき
CREATE TABLE order_items (
id INT PRIMARY KEY,
order_id INT REFERENCES orders(id) ON DELETE CASCADE
);
-- 2. customers と orders (顧客と注文履歴)
-- -> 注文履歴を残したいので、顧客を消そうとしたら拒否
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_id INT REFERENCES customers(id) ON DELETE RESTRICT
);
-- 3. users と tasks (担当者と作業)
-- -> 担当者が退職しても作業は残す、担当不在にして表示
CREATE TABLE tasks (
id INT PRIMARY KEY,
assignee_id INT REFERENCES users(id) ON DELETE SET NULL
);この 3 つはどれも親子関係ですが、ポリシーの選択は 「親の不在を子はどう受け取るべきか」 という業務要件で決まります。
選び方のチートシート
判断の軸はシンプルです。子レコードが「親なしでは存在意味がないか」「履歴として残したいか」「担当不在の形で残したいか」のどれかに当てはまります。
注意点
- SET NULL を使うには、FK 列が
NULLを許容している必要があります。NOT NULL制約と両立しません。 - CASCADE は連鎖します。
orders -> order_itemsが CASCADE で、customers -> ordersも CASCADE だと、顧客削除で明細まで一気に消えます。意図的かを確認しましょう。 - RESTRICT は安全側ですが、親を消したいときに子を先に消さないといけないので、運用手順が増えます。
比較表
プレーンテキスト
CASCADE RESTRICT SET NULL
親 DELETE 時 子も消える エラーで拒否 子の FK を NULL に
親 UPDATE 時 子も更新される エラーで拒否 子の FK を NULL に
子 FK 列 NOT NULL OK NOT NULL OK NULL 許可必須
典型例 明細, 添付 顧客と注文 担当者, レビュアCASCADE は「子は親の部分」、RESTRICT は「親は保護対象」、SET NULL は「親不在でも子は独立」。この 3 つの意味合いで使い分けると迷わない。
意図せず CASCADE を選んでしまうと、親削除のたびに膨大な子データが消える事故になりかねません。本番投入前に必ず DELETE のリハーサルをし、影響件数を確認しましょう。
連鎖 CASCADE の落とし穴
CASCADE は親 -> 子だけでなく、子 -> 孫テーブルへもさらに波及します。
SQL クエリ
-- customers -> orders -> order_items が全て CASCADE
DELETE FROM customers WHERE id = 1;
-- 田中の注文も明細も一気に消える小さい DB では便利ですが、たとえば 1 顧客に数万件の注文・数十万件の明細がぶら下がっている本番では、1 件の DELETE が数秒〜数十秒のロック時間を発生させることがあります。CASCADE は「設計上の意図」と「物理的なデータ量」両方を念頭に置いて選ぶべきです。
RESTRICT 採用時の運用フロー
RESTRICT を採用すると、親を削除する手順は次のように増えます。
SQL クエリ
-- 1) 子テーブルを先に始末
DELETE FROM orders WHERE customer_id = 1;
-- 2) その後で親を削除
DELETE FROM customers WHERE id = 1;アプリ側にこの順序を強制する責任が出てきますが、その代わり「うっかり親削除で履歴が吹き飛ぶ」事故は確実に防げます。安全性と運用負荷のトレードオフをチーム内で合意してから決めましょう。
SET NULL を選ぶときの設計確認
SET NULL を採用するなら、NULL のときの業務的な意味付けを明確にする必要があります。たとえば tasks.assignee_id が NULL なら「未アサイン」と扱うのか「アサイン待ち」と扱うのかで、画面表示やフィルタ条件が変わってきます。NULL を「担当不在」を表すフラグとして使うなら、その意味をテーブルコメントなどで明示しておくと運用が楽です。
まとめ
- CASCADE は「子は親の付属物」、明細・添付・履歴削除に向く
- RESTRICT は「親を保護」、顧客・商品マスタなど重要データの誤削除を防ぐ
- SET NULL は「親なしでも子は存続」、担当者・レビュアなどの関係に向く
- SET NULL は FK 列が NULL 許可であることが必要
- CASCADE は孫テーブルまで連鎖するのでデータ量も意識する
- RESTRICT 採用時は「子を先に消す」運用フローを徹底する
次のレッスン
次は 物理FK vs 論理FK です。物理外部キーと論理外部キーの違いをSQL操作を通して学びます。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- CASCADE/RESTRICT/SET NULL の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. CASCADE/RESTRICT/SET NULL とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
テーブル構造
schema.sql
CREATE TABLE users (
id INT PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE tasks (
id INT PRIMARY KEY,
title TEXT NOT NULL,
assignee_id INT,
CONSTRAINT fk_tasks_assignee
FOREIGN KEY (assignee_id) REFERENCES users(id)
ON DELETE SET NULL
);
INSERT INTO users (id, name) VALUES
(1, '田中'),
(2, '佐藤'),
(3, '鈴木');
INSERT INTO tasks (id, title, assignee_id) VALUES
(101, '見積もり作成', 1),
(102, '請求書送付', 1),
(103, '顧客訪問', 2),
(104, '報告書レビュー', 3);
期待される出力
| id | title | assignee_id |
|---|---|---|
| 101 | 見積もり作成 | NULL |
| 102 | 請求書送付 | NULL |
| 103 | 顧客訪問 | 2 |
| 104 | 報告書レビュー | 3 |