外鍵 是關係型資料庫區別於檔案系統、NoSQL 的核心特徵之一。它在資料庫層面強制執行 **參照完整性**,確保「子表記錄必須指向存在的父表記錄」。本文系統講解概念、語法、行為模式、效能影響與架構選型建議。
-- 父表 (被參照表)
CREATE TABLE categories (
category_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE
) ENGINE=InnoDB;
-- 子表 (參照表)
CREATE TABLE articles (
article_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
category_id TINYINT UNSIGNED NOT NULL,
title VARCHAR(200) NOT NULL,
-- 定義外鍵
CONSTRAINT fk_article_category
FOREIGN KEY (category_id) REFERENCES categories(category_id)
ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB;
| 動作 | 行為 | 適用場景 | 風險 |
|---|---|---|---|
| RESTRICT (預設) | 拒絕父表刪除/更新,報錯 `ERROR 1451 (23000)` | 核心主數據 (用戶、商品、訂單分類),嚴禁級聯刪除 | 需應用層先處理子表 |
| CASCADE | 自動刪除/更新子表對應記錄 | 明確從屬關係 (訂單→訂單項、文章→評論)、臨時/快照數據 | **極危險**:誤刪父表引發連鎖刪除、觸發器連鎖、鎖升級、備份恢複雜 |
| SET NULL | 子表外鍵列設為 NULL (要求列允許 NULL) | 弱關聯 (用戶刪除後文章保留但作者置空)、軟刪除輔助 | 語義模糊、查詢需處理 NULL |
| NO ACTION | 標準 SQL 等同 RESTRICT,MySQL 內部實現相同 | 同 RESTRICT | 同 RESTRICT |
| SET DEFAULT | 子表外鍵列設為預設值 (要求列有 DEFAULT) | 極少用,預設值必須存在於父表 | 同 SET NULL |
-- 建表時定義
CREATE TABLE child (
id INT PRIMARY KEY,
parent_id INT,
CONSTRAINT fk_child_parent FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE CASCADE
) ENGINE=InnoDB;
-- ALTER TABLE 新增 (需先有索引)
ALTER TABLE child ADD INDEX idx_parent_id (parent_id);
ALTER TABLE child ADD CONSTRAINT fk_child_parent FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE SET NULL;
-- 刪除外鍵
ALTER TABLE child DROP FOREIGN KEY fk_child_parent;
-- 注意:DROP FOREIGN KEY 只刪除約束,**不自動刪除索引**,需額外 `DROP INDEX idx_parent_id`。
-- 查看外鍵資訊
SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_TYPE = 'FOREIGN KEY' AND TABLE_SCHEMA = 'my_db';
SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_NAME IS NOT NULL AND TABLE_SCHEMA = 'my_db';
| 維度 | 資料庫外鍵 | 應用層維護 (軟外鍵) |
|---|---|---|
| 資料一致性保障 | **強一致** (ACID)、不可繞過 | 最終一致、依賴代碼正確性、事務邊界 |
| 效能開銷 | 額外鎖、索引維護、檢查開銷 | 零資料庫開銷、應用層邏輯成本 |
| 彈性/演進 | DDL 變更困難 (鎖表、級聯)、跨庫不支援 | 極靈活、微服務友好、跨庫/跨服務可行 |
| 除錯/可觀測 | 錯誤碼標準、資料庫日誌可見 | 需應用層日誌、分散式追蹤 |
| 適用場景 | 核心主數據、強一致要求、單體/少服務、團隊 DBA 強 | 微服務、高併發寫入、跨庫、團隊全棧、最終一致可接受 |
-- 1. 缺少索引的外鍵列 (子表)
SELECT kcu.TABLE_NAME, kcu.COLUMN_NAME, kcu.CONSTRAINT_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
LEFT JOIN INFORMATION_SCHEMA.STATISTICS s
ON s.TABLE_SCHEMA = kcu.TABLE_SCHEMA
AND s.TABLE_NAME = kcu.TABLE_NAME
AND s.COLUMN_NAME = kcu.COLUMN_NAME
AND s.SEQ_IN_INDEX = 1
WHERE kcu.REFERENCED_TABLE_NAME IS NOT NULL
AND kcu.TABLE_SCHEMA = 'my_db'
AND s.INDEX_NAME IS NULL;
-- 2. CASCADE 深度超過 3 層的鏈路 (風險預警)
WITH RECURSIVE fk_chain AS (
SELECT kcu.TABLE_NAME, kcu.COLUMN_NAME, kcu.REFERENCED_TABLE_NAME, 1 AS depth
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
WHERE kcu.REFERENCED_TABLE_NAME IS NOT NULL AND kcu.TABLE_SCHEMA = 'my_db'
UNION ALL
SELECT kcu.TABLE_NAME, kcu.COLUMN_NAME, kcu.REFERENCED_TABLE_NAME, fc.depth + 1
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
JOIN fk_chain fc ON fc.REFERENCED_TABLE_NAME = kcu.TABLE_NAME
WHERE kcu.TABLE_SCHEMA = 'my_db'
)
SELECT * FROM fk_chain WHERE depth > 3;
外鍵是把雙刃劍:**用得好是資料質量的守護神,用不好是效能與演進的絆腳石。** 理解其鎖行為、級聯風險、架構邊界,在「強一致兜底」與「高併發彈性」之間做出清醒選擇,才是成熟架構師的標誌。