外部キーのエラーを、削除や無効化で消さない
外部キーは列同士の参照条件を表す制約です。親を削除できないとき、CASCADEや子行削除を選べばよいとは限りません。制約を外すこと、索引を外すこと、行を削除することはそれぞれ別の操作です。まず現状を読取り、データ保持の要件から判断します。
1. 新しい隔離検体と、接続の条件を確認する
SELECT VERSION() AS server_version, DATABASE() AS selected_database,
@@SESSION.sql_mode AS session_sql_mode,
@@SESSION.foreign_key_checks AS session_fk_checks;
この例は新しい空DB cwiki_w25内だけで実行した教材です。parents/childrenの同名表は未作成で、いずれもInnoDBを使います。実データへこの作成/INSERTを実行しません。親idと子parent_idは同じINT UNSIGNEDで、子はNULLも許可します。
-- New empty cwiki_w25 fixture DB only, with no pre-existing names below.
CREATE TABLE cwiki_w25.parents (id INT UNSIGNED NOT NULL PRIMARY KEY) ENGINE=InnoDB;
CREATE TABLE cwiki_w25.children (
id INT NOT NULL PRIMARY KEY, parent_id INT UNSIGNED NULL
) ENGINE=InnoDB;
INSERT INTO cwiki_w25.parents VALUES (1);
INSERT INTO cwiki_w25.children VALUES (1,1),(2,NULL);
2. 制約の実名と列の対応を読む
SHOW CREATE TABLE cwiki_w25.children;
SELECT CONSTRAINT_SCHEMA, CONSTRAINT_NAME, TABLE_NAME, COLUMN_NAME,
ORDINAL_POSITION, REFERENCED_TABLE_SCHEMA,
REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = 'cwiki_w25' AND TABLE_NAME = 'children'
AND REFERENCED_TABLE_NAME IS NOT NULL
ORDER BY CONSTRAINT_NAME, ORDINAL_POSITION;
SELECT CONSTRAINT_NAME, UPDATE_RULE, DELETE_RULE
FROM information_schema.REFERENTIAL_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = 'cwiki_w25' AND TABLE_NAME = 'children'
ORDER BY CONSTRAINT_NAME;
制約名だけでなく、schema/表/列・参照先schema/表/列と並び順を読みます。同じ名前が別schemaや表に存在する場合も考慮し、文字列の一致だけで結合しません。複合外部キーなら全列を対応する順で照合します。TABLE_CONSTRAINTSは制約の種類等を調べる情報で、それだけでは列ごとの参照先を示せません。
3. 参照切れを、変更せずに探す
-- One-column nullable relation only; use all key columns for a composite FK.
SELECT c.id, c.parent_id
FROM cwiki_w25.children AS c
LEFT JOIN cwiki_w25.parents AS p ON p.id = c.parent_id
WHERE c.parent_id IS NOT NULL AND p.id IS NULL
ORDER BY c.id;
この単一列の関係ではparent_idがNULLの行を参照切れとして数えません。初期検体では結果は0行でした。実表では対象の意味と負荷/権限/同時更新を考慮します。複合キーではこの1列のSQLを流用せず、全キー列とNULLの規則を確認してください。
結果があった場合も、即座に子行を消す、架空の親を作る、任意の値へ置換する手順にはしません。誤った接続先/結合条件/データの供給元も調べ、業務上どの関係が正しいかを決めます。0件の読取は、後からの並行書込も含めた整合性保証ではありません。
4. 照合済みの検体へRESTRICTを付ける
-- Fresh matching INT UNSIGNED columns and reviewed existing fixture rows only.
ALTER TABLE cwiki_w25.children
ADD CONSTRAINT fk_w25_parent FOREIGN KEY (parent_id)
REFERENCES cwiki_w25.parents(id)
ON DELETE RESTRICT ON UPDATE RESTRICT;
検体では親1が存在する子行とNULLの子行を保持して追加できました。以後、参照されている親1のDELETEは1451、存在しない親を参照するINSERTは1452で拒否されました。これは保持する設計どおりの結果で、エラーを消すために保護を弱めません。
別の使い捨て関係でCASCADEを試すと親行の削除で子行も消え、SET NULLなら子行は残って参照列がNULLになりました。どちらもRESTRICTの代わりの万能策ではありません。関連データの保持、NULLの意味、連鎖する別の関係まで考えて選びます。
5. 制約の削除は、行や索引の削除ではない
SELECT INDEX_NAME, COLUMN_NAME, SEQ_IN_INDEX
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = 'cwiki_w25' AND TABLE_NAME = 'children'
ORDER BY INDEX_NAME, SEQ_IN_INDEX;
この検体ではFKを追加した際、子parent_id用の索引が自動作成されました。互換の索引が既にある場合や製品/engineの条件は別に確認します。必要な索引を先に消そうとすると1553で拒否されました。
次は制約を変更するSQLです。自分で作った検体のfk_w25_parentだけを対象にしています。実表での実行や、エラー回避のための制約解除を勧めるものではありません。
-- Remove only this disposable fixture constraint, not rows or indexes.
ALTER TABLE cwiki_w25.children DROP FOREIGN KEY fk_w25_parent;
検体では制約だけがなくなり、既存行と索引はそのままでした。一方、制約がなくなると参照切れのINSERTを受け入れ、再追加は既存の参照切れにより1452で失敗しました。戻すSQLが書けても、削除後のデータを含めて元へ戻せる保証ではありません。DROP INDEXを必ず続けて実行する必要もありません。
6. 再有効化と再検査、遅延と無効化を区別する
FOREIGN_KEY_CHECKSを0にする旧例は、通常のインポート高速化やエラー解消の手順として勧めません。隔離した負の検査では、この接続だけ無効にして挿入した参照切れは、1へ戻しても両製品で残りました。その後の新しい参照切れINSERTは1452で拒否されました。「チェックONへ戻した」と「過去行も検査済み」は別です。他の新規接続の設定は1のままで、sessionの条件も混同しません。
PostgreSQLのSET CONSTRAINTSは、DEFERRABLEな制約の検査時機を変える命令で、全FKの無効化ではありません。NOT DEFERRABLEは対象外で、遅延しても整合性の要件がなくなるわけではありません。NO ACTION/RESTRICTの遅延可否もInnoDBと同一視しません。PostgreSQLの旧比較SQLは保持していますが、今回実DBで再実行したものではありません。
旧LaravelのforeignId/constrainedは参照型や命名/対象表を確認し、CASCADEを標準の安全策にしません。dropForeignの配列指定と実名指定も代替例で、同じ制約へ順次両方を実行しません。CWiki本体のmigrate、既存FKやアプリの変更は行っていません。
旧例の注意:参照元/先の方向と実名を確認し、列/索引/制約を同じものと扱いません。CASCADE等は保持する業務データへの作用から選びます。
| この記事の要点 |
|
外部キーの追加
旧例の注意:名前のない追加/同名制約付き追加/CASCADE/SET NULL/CREATEは別例で、同じordersへ順次実行しません。親子の型/符号/索引と既存行も先に照合します。
-- 基本構文
ALTER TABLE orders
ADD FOREIGN KEY (user_id) REFERENCES users(id);
-- 制約名を明示(推奨:あとで削除が楽)
ALTER TABLE orders
ADD CONSTRAINT fk_orders_user_id
FOREIGN KEY (user_id) REFERENCES users(id);
-- カスケード動作付き
ALTER TABLE orders
ADD CONSTRAINT fk_orders_user_id
FOREIGN KEY (user_id) REFERENCES users(id)
ON DELETE CASCADE
ON UPDATE CASCADE;
-- 削除時は NULL に
ALTER TABLE orders
ADD CONSTRAINT fk_orders_user_id
FOREIGN KEY (user_id) REFERENCES users(id)
ON DELETE SET NULL;
-- テーブル作成時に同時定義
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT,
amount DECIMAL(10, 2),
CONSTRAINT fk_orders_user_id
FOREIGN KEY (user_id) REFERENCES users(id)
);
外部キー制約名の確認
MySQL
旧例の注意:schemaと列の並びも照合します。SHOW CREATEの全文を公開せず、TABLE_CONSTRAINTSだけで列対応まで分かったと扱いません。
-- SHOW CREATE TABLE
SHOW CREATE TABLE orders;
-- → 出力中の CONSTRAINT `fk_xxx` FOREIGN KEY ... を確認
-- information_schema 経由
SELECT
CONSTRAINT_NAME,
TABLE_NAME,
COLUMN_NAME,
REFERENCED_TABLE_NAME,
REFERENCED_COLUMN_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = 'mydb'
AND TABLE_NAME = 'orders'
AND REFERENCED_TABLE_NAME IS NOT NULL;
-- カスケード設定込みで確認
SELECT
CONSTRAINT_NAME,
UPDATE_RULE,
DELETE_RULE
FROM information_schema.REFERENTIAL_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = 'mydb'
AND TABLE_NAME = 'orders';
PostgreSQL
旧例の注意:旧PG queryはschemaや複合キーの列順が不足し得ます。同じconstraint_nameだけのJOINを、全schema/列対応の完全な照会として使いません。今回はPG実行未検証です。
-- psql の \d
\d orders
-- system catalog
SELECT
conname,
pg_get_constraintdef(c.oid)
FROM pg_constraint c
JOIN pg_class t ON c.conrelid = t.oid
WHERE t.relname = 'orders'
AND c.contype = 'f';
-- information_schema
SELECT
tc.constraint_name,
kcu.column_name,
ccu.table_name AS foreign_table,
ccu.column_name AS foreign_column
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage ccu
ON ccu.constraint_name = tc.constraint_name
WHERE tc.table_name = 'orders'
AND tc.constraint_type = 'FOREIGN KEY';
外部キーの削除
MySQL
旧例の注意:実行しない旧削除例です。DROP FOREIGN KEYは行/索引を消す操作ではなく、DROP INDEXを必ず続けません。実際に必要な索引やFK保護を勝手に外しません。
-- 制約名を指定して削除
ALTER TABLE orders DROP FOREIGN KEY fk_orders_user_id;
-- 暗黙的に作られたインデックスは別途削除(必要に応じて)
ALTER TABLE orders DROP INDEX fk_orders_user_id;
PostgreSQL / 標準 SQL
旧例の注意:PG用の制約削除例です。MySQLへ同じ構文を一律適用せず、対象/依存/新しいwriterへの影響を別に確認します。
ALTER TABLE orders DROP CONSTRAINT fk_orders_user_id;
ON DELETE / ON UPDATE のオプション
旧例の注意:NO ACTION/RESTRICTの遅延可否やSET DEFAULTの対応は製品/engine別です。CASCADE/SET NULLも、エラーを消すために機械選択しません。
| オプション | 動作 |
|---|---|
| NO ACTION (デフォルト) | 参照中の親削除/更新を拒否する条件。無条件の安全保証ではない |
| RESTRICT | InnoDBでは即時判定。PostgreSQLの遅延可能なNO ACTIONとは区別 |
| CASCADE | 参照先と同じ動作を伝播(親削除なら子も削除) |
| SET NULL | 参照元を NULL に書き換える(NULL 許可カラムのみ) |
| SET DEFAULT | 製品/engine別に対応条件を確認。InnoDBは非対応でPGだけの機能という意味ではない |
外部キー一時無効化(インポート時等)
旧例の注意:実行しない旧比較例です。FKチェックをONへ戻しても過去行を自動再検査しません。PGのSET CONSTRAINTSはDEFERRABLEな検査時機の変更で全保護の無効化ではありません。TRUNCATEや大量投入の定型にしません。
-- MySQL: セッション単位
SET FOREIGN_KEY_CHECKS = 0;
-- 大量 INSERT / TRUNCATE 等
SET FOREIGN_KEY_CHECKS = 1; -- 戻す
-- PostgreSQL: トランザクション内で
BEGIN;
SET CONSTRAINTS ALL DEFERRED;
-- ... 処理
COMMIT;
-- ★ 注意: 制約を切ると整合性が失われる。データ整合確認後に有効化
Laravel マイグレーションでの書き方
旧例の注意:旧Laravelの型/実名/参照先とCASCADEの要否を確認します。down内の配列/名前指定は代替例で順次実行せず、制約削除をデータ復元と混同しません。
// マイグレーション: 外部キー追加
public function up() {
Schema::table('orders', function (Blueprint $table) {
// 短縮形(Laravel 7+)
$table->foreignId('user_id')->constrained()->onDelete('cascade');
// 明示的に書く場合
$table->unsignedBigInteger('product_id');
$table->foreign('product_id')
->references('id')->on('products')
->onDelete('restrict')
->onUpdate('cascade');
});
}
// マイグレーション: 外部キー削除
public function down() {
Schema::table('orders', function (Blueprint $table) {
// 命名規則: テーブル名_カラム名_foreign
$table->dropForeign(['user_id']);
// または制約名指定
$table->dropForeign('orders_user_id_foreign');
});
}
外部キー追加でエラーになる典型例
旧例の注意:エラーの条件を読取で特定します。拒否された親削除を通すためにCASCADEへ変えたり、子を削除したりしません。
| エラー | 原因 |
|---|---|
| Cannot add foreign key constraint | 表/engine/型/符号/索引/charset/collation/権限等を確認し、文言だけで一原因に断定しない |
| Foreign key constraint fails | 参照切れ候補を読取で調査。子行削除や架空の親作成で自動的に解消しない |
| Duplicate foreign key constraint name | 既存の制約/対象schema/表を照合。同じ関係を別名で重複追加しない |
| Cannot delete or update a parent row | 参照関係と保持要件を確認。CASCADE/子行削除へ自動的に切り替えない |
パフォーマンスへの影響
- 索引との関係: 対応engineでは必要な索引がなければ作成される場合がある。既存互換索引と実名を確認
- INSERT / UPDATE 時に参照チェック: 親テーブルの存在確認で若干のオーバーヘッド
- 投入時の負荷: 件数だけで固定せず計測する。通常の高速化として制約を無効化しない
- カスケード削除は重い: 数十万行の連鎖削除は時間がかかる
- 本番運用は注意深く: アプリ側ロジックで整合性を保つ方が柔軟な場合も
元の書式付き外部キーSQL
元の書式付きSQL:追加
元例の注意:日本語は置換箇所です。schema/列の対応・型/符号・索引・既存行を照合し、エラー回避のために制約を追加し直しません。
|
ALTER TABLE テーブル名 ADD FOREIGN KEY(カラム名) REFERENCES 参照先テーブル名(参照先カラム名); |
元の書式付きSQL:実名の確認
元例の注意:SHOW CREATEは実定義の確認です。必要な対象だけ読み、内部情報を含む全文を公開しません。
|
SHOW CREATE TABLE テーブル名; |
元の書式付きSQL:制約の削除
元例の注意:制約名は列名/索引名と区別します。DROPは関係の保護を外す操作で、行や索引の削除を続ける必須手順ではありません。
|
ALTER TABLE テーブル名 DROP FOREIGN KEY 外部キー制約名; |
確認範囲と関連ページ
2026年10月26日確認。新13SQLファイルを新規tmpfs MySQL8.4.13/MariaDB11.8.9-ubu2404で実行し各17/計34試験に成功。追加と既存値/後続INSERT、MDL、削除と再追加/ROLLBACK/view、IF EXISTS、監視catalog、FK実名/参照切れ/NULL/RESTRICT/索引/再追加を検査。CASCADE/SET NULLと無効中の参照切れ/再有効化も別の負の検査で確かめました。無効化の新しい実行SQLを記事へ追加していません。本番DDL/データ/設定/計測機能/アプリや既存認証は変更していません。全製品/版/型/engine/複合FK/大規模負荷・無停止/復旧、PG/旧全SQL/Laravel/外部ツールの実行保証ではありません。新2図の900/360px静止画で文字枠を確認し、Work設定でブラウザ無効のため実ブラウザ/実端末は未検証です。
テーブル関連の親ページから子ページを選択できます。カラム追加/削除、外部キー、作成、定義変更、名前変更、製品配下のSQL、MySQL/MariaDBも参照してください。
一次資料:MySQL ALTERの構文/属性、MariaDB ALTERとIF EXISTS、MySQL FKと再有効化時の非再検査、Online DDLとmetadata lock、Performance SchemaでのALTER計測条件、MariaDBのPROCESSLIST/進捗、PG SET CONSTRAINTS、PGの参照アクション、Laravel13の列/外部キー、ptの書込み/切替/dry-run、gh-ostの要件/制約。
子ページはありません
- テーブル作成
- カラムの定義変更
- カラム名の変更
- カラム追加 / 削除
- インデックスの作成
- インデックスの削除
- AUTO_INCREMENTの仕様について
- AUTO_INCREMENTの追加と確認方法
- 外部キーの追加/削除と制約名の確認方法
人気ページ
- 1 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 3Dグラフィックスとは|モデリング/レンダリング/主要ソフトウェア (Blender / Maya)
- 7 Spring Frameworkのアノテーション一覧
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- MySQL/MariaDBのテーブル一覧|表・ビュー・権限と絞り込み 2026-10-26 07:32:39
- MySQL/MariaDBのDB選択|USEと接続・既定DBの違い 2026-10-26 07:32:39
- MySQL/MariaDBのDB一覧|SHOW DATABASESと権限の違い 2026-10-26 07:32:39
- MySQLへの接続|接続先・認証・TLSとエラーの確認 2026-10-26 07:32:39
- MySQL/MariaDBのカラム定義確認|権限・既定値・ORMとの違い 2026-10-26 06:18:53
- MySQL/MariaDBのメタデータ|列・索引・制約の確認ガイド 2026-10-26 06:18:53
- MySQL/MariaDBのテーブル定義確認|DESC・SHOW CREATEと復旧の範囲 2026-10-26 06:18:53
- MySQL/MariaDBのAUTO_INCREMENT追加・確認|既存IDと属性の保持 2026-10-26 05:42:11
- MySQL/MariaDBのAUTO_INCREMENT|欠番・取得ID・カウンタの違い 2026-10-26 05:42:11
- MySQL/MariaDBの索引削除|不可視化・UNIQUE・外部キーの注意 2026-10-26 05:11:35
- MySQL/MariaDBの索引作成|結果比較・UNIQUE・実行計画の確認 2026-10-26 05:11:35
- MySQL/MariaDBの外部キー|制約名・参照切れ・削除と再検査 2026-10-26 04:51:08
- MySQL/MariaDBのカラム追加・削除|既存値・互換性・ロックの確認 2026-10-26 04:51:08
- MySQL/MariaDBのカラム名変更|RENAME COLUMNと利用側の確認 2026-10-26 04:24:55
- MySQL/MariaDBのカラム定義変更|MODIFYの属性保持とロック 2026-10-26 04:24:55