◀ 9.

MySQL/MariaDBの外部キー|制約名・参照切れ・削除と再検査

外部キーのエラーを、削除や無効化で消さない

制約・索引・行と、再有効化・過去行再検査を区別する図

外部キーは列同士の参照条件を表す制約です。親を削除できないとき、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等は保持する業務データへの作用から選びます。

この記事の要点
  • SQL で外部キーの追加・削除と制約名の確認方法
  • 追加: ALTER TABLE t ADD FOREIGN KEY (col) REFERENCES other(col)
  • 制約名確認: information_schema.TABLE_CONSTRAINTS または SHOW CREATE TABLE
  • 削除: ALTER TABLE t DROP FOREIGN KEY 制約名(MySQL)/ DROP CONSTRAINT(PostgreSQL)
  • カスケード: ON DELETE CASCADE / ON UPDATE 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 (デフォルト)参照中の親削除/更新を拒否する条件。無条件の安全保証ではない
RESTRICTInnoDBでは即時判定。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の要件/制約。

子ページ

子ページはありません

同階層のページ
  1. テーブル作成
  2. カラムの定義変更
  3. カラム名の変更
  4. カラム追加 / 削除
  5. インデックスの作成
  6. インデックスの削除
  7. AUTO_INCREMENTの仕様について
  8. AUTO_INCREMENTの追加と確認方法
  9. 外部キーの追加/削除と制約名の確認方法