2026年9月29日
2026年9月29日
MySQLの外部キー(FOREIGN KEY)制約を設定する方法|作成・確認・削除、ON DELETEの選び方と追加できないときの対処
MySQL の外部キー制約は、子テーブルの列に「親テーブルに存在する値しか入れられない」という約束を付け、存在しない親を指すデータ(孤立データ)が生まれるのを防ぐ仕組みです。この記事では、外部キーを作る前の条件、作成・確認・削除の SQL、親を消したときの動作(ON DELETE / ON UPDATE)の選び方、追加に失敗したときの原因の調べ方を順に説明します。WordPress のプラグインで独自テーブルを作る場合の注意点も最後に触れます。
外部キーを作る前に満たす条件
外部キーは、どのテーブルにでも付けられるわけではありません。次の条件を満たしていないと作成に失敗します。
- ストレージエンジンが InnoDB であること。 MyISAM のテーブルでは外部キーの記述が受け付けられても、制約としては機能しません。
SHOW TABLE STATUS LIKE 'テーブル名';の Engine 列で確認します。 - 親と子の列の型が揃っていること。 整数型は大きさと符号(UNSIGNED かどうか)まで一致させます。
BIGINT UNSIGNEDの親にINTの子は付けられません。文字列型は長さが違ってもかまいませんが、文字セットと照合順序は揃える必要があります。 - 親側の列にインデックスがあること。 通常は主キーか UNIQUE キーを参照します。MySQL 8.4 では、一意でないキーや複合キーの一部を参照する外部キーが既定で制限されるようになっています(2026年10月現在。
restrict_fk_on_non_standard_keyという設定で制御されます)。子側の列にインデックスがなければ、作成時に自動で作られます。 - パーティション分割したテーブルでないこと。 InnoDB では、ユーザー定義のパーティションを使うテーブルに外部キーは付けられません。
テーブル作成時に外部キーを付ける
注文(orders)と注文明細(order_items)を例にします。明細はいずれかの注文に属する、という関係です。
CREATE TABLE orders (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
created_at DATETIME NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE order_items (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
order_id BIGINT UNSIGNED NOT NULL,
sku VARCHAR(64) NOT NULL,
qty INT UNSIGNED NOT NULL,
PRIMARY KEY (id),
KEY idx_order_id (order_id),
CONSTRAINT fk_order_items_order
FOREIGN KEY (order_id) REFERENCES orders (id)
ON DELETE CASCADE
ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CONSTRAINT の後の名前は省略もできますが、自分で付けておくと、削除やエラーの調査のときにどの制約か分かりやすくなります。テーブル名と列名を含めた命名にしておくのが実用的です。
ON DELETE / ON UPDATE の選び方
親の行が削除・更新されたとき子の行をどうするかを指定します。InnoDB で使えるのは次の4つです。
- RESTRICT / NO ACTION: 子が残っている親は削除・更新できない。InnoDB では両者は同じ動作で、指定を省略した場合もこの動作になります
- CASCADE: 親を消すと子も消える。親のキーを更新すると子の値も追随する
- SET NULL: 親を消すと子の列が NULL になる。子の列が NULL を許可している必要があります
SET DEFAULT は構文としては存在しますが、InnoDB では使えません。
選び方の目安は、子のデータが「親なしでは意味を持たないか」です。注文明細のように親とともに消えてよいものは CASCADE、記事と執筆者のように「執筆者が消えても記事は残したい」ものは SET NULL、請求データのように誤って消えると困るものは RESTRICT にして、アプリケーション側で順序立てて削除させます。
CASCADE は便利ですが、親を1行消しただけで大量の子が連鎖して消える可能性があります。また、連鎖して行われた削除や更新ではトリガーが動きません。子の削除をトリガーで記録している場合は、記録が漏れる点に注意してください。
既存のテーブルに外部キーを追加する
運用中のテーブルに後から付ける場合は、先に孤立データがないかを調べます。孤立データがあると追加は失敗します。
SELECT oi.id, oi.order_id
FROM order_items AS oi
LEFT JOIN orders AS o ON o.id = oi.order_id
WHERE o.id IS NULL;
結果が0件であることを確かめてから追加します。孤立データが見つかった場合、削除するか正しい親に付け替えるかは業務の判断なので、勝手に消さずに関係者と確認してください。
ALTER TABLE order_items
ADD CONSTRAINT fk_order_items_order
FOREIGN KEY (order_id) REFERENCES orders (id)
ON DELETE CASCADE;
外部キーの追加は、通常はテーブル全体のコピーを伴う処理になり、大きなテーブルでは時間がかかり、その間の書き込みにも影響します。実行前にバックアップを取り、同じデータ量の検証環境で所要時間を測ってから、本番はアクセスの少ない時間帯に行います。
設定した外部キーを確認・削除する
付いている外部キーは SHOW CREATE TABLE で確認できます。データベース全体を一覧したいときは information_schema を使います。
SHOW CREATE TABLE order_items\G
SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME,
REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = DATABASE()
AND REFERENCED_TABLE_NAME IS NOT NULL;
ON DELETE などの設定は information_schema.REFERENTIAL_CONSTRAINTS の DELETE_RULE、UPDATE_RULE 列で確認できます。
削除は DROP FOREIGN KEY で行います。外部キーのために作られたインデックスは残るため、不要なら別に削除します。
ALTER TABLE order_items DROP FOREIGN KEY fk_order_items_order;
ALTER TABLE order_items DROP INDEX idx_order_id; -- 不要な場合のみ
追加や更新に失敗したときの調べ方
外部キーに関するエラーは、文言でおおよその原因が分かります。
**Cannot add or update a child row: a foreign key constraint fails** は、子に入れようとした値が親に存在しない状態です。挿入の順序(親を先に入れる)と、値の取り違えを確認します。既存テーブルへの追加時に出た場合は、前述の孤立データの検索を行います。
**Cannot delete or update a parent row** は、RESTRICT の親を子が残ったまま消そうとした状態です。子を先に削除するか、仕様として CASCADE が妥当かを見直します。
外部キーの作成自体に失敗する場合(MySQL 8.0 では「列が互換でない」「インデックスがない」といった趣旨のメッセージ、古い版では主にエラー 1215 の Cannot add foreign key constraint)は、前述の条件を順に確認します。よくある原因は、整数の UNSIGNED の不一致、文字列の照合順序の不一致、エンジンが MyISAM、親側にインデックスがない、です。詳しい理由は次のコマンドの LATEST FOREIGN KEY ERROR の欄に出ます。
SHOW ENGINE INNODB STATUS\G
データの一括インポートで親子の順序を守れないときは、セッション内で SET FOREIGN_KEY_CHECKS = 0; として一時的に検査を止める方法があります。ただし、検査を戻しても投入済みのデータは再検査されないため、インポート後に孤立データの検索を行ってください。
WordPress で使うときの注意
WordPress のコアテーブル(wp_posts、wp_postmeta など)には外部キーが定義されておらず、削除や更新の順序はコアの PHP 側で制御されています。コアテーブルに外部キーを後付けすると、コアやプラグインの処理順と合わずにエラーになるおそれがあるため、避けるのが無難です。
外部キーが活きるのは、プラグインで作る独自テーブルの間です。wp_posts を参照する場合、ID 列は BIGINT UNSIGNED なので子の列も同じ型にします。テーブルの作成に使う dbDelta() は外部キーの記述をうまく扱えないことがあるため、テーブル作成後に $wpdb->query() で ALTER TABLE を実行し、事前に SHOW CREATE TABLE などで既に付いているかを確認してから追加する、という形が扱いやすいです。
まとめ
- 外部キーは InnoDB のテーブルで、型・符号・照合順序を揃え、親側にインデックスがある列に付ける
- ON DELETE は、子が親なしで意味を持たないなら CASCADE、残したいなら SET NULL、誤削除を防ぎたいなら RESTRICT
- 既存テーブルに追加する前に LEFT JOIN で孤立データを探し、バックアップと検証環境での所要時間の確認を行う
- 失敗の理由は
SHOW ENGINE INNODB STATUSの LATEST FOREIGN KEY ERROR で確認する - WordPress のコアテーブルには付けず、プラグインの独自テーブル間で使う