MySQL 支援外鍵,允許跨資料表交叉參照相關資料,以及外鍵約束,有助於保持相關資料的一致性。
外鍵關係涉及一個持有初始欄位值的父資料表,以及一個欄位值參照父資料表欄位值的子資料表。外鍵約束定義在子資料表上。
在 CREATE TABLE
或 ALTER TABLE
語法中定義外鍵約束的基本語法包括以下內容
[CONSTRAINT [symbol]] FOREIGN KEY
[index_name] (col_name, ...)
REFERENCES tbl_name (col_name,...)
[ON DELETE reference_option]
[ON UPDATE reference_option]
reference_option:
RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT
本節將在以下主題下描述外鍵約束的用法
外鍵約束命名受以下規則約束
如果已定義,則會使用
CONSTRAINT
symbol
值。如果未定義
CONSTRAINT
symbol
子句,或者在CONSTRAINT
關鍵字後未包含符號,則會自動產生約束名稱。如果未定義
CONSTRAINT
symbol
子句,或者在CONSTRAINT
關鍵字後未包含符號,則InnoDB
和NDB
儲存引擎都會忽略FOREIGN_KEY
。index_name
如果已定義,則
CONSTRAINT
值在資料庫中必須是唯一的。重複的symbol
symbol
會導致類似以下的錯誤:錯誤 1005 (HY000):無法建立資料表 'test.fk1' (錯誤碼:121)。NDB Cluster 使用建立時相同的大小寫字母儲存外鍵名稱。
在 FOREIGN KEY ... REFERENCES
子句中的資料表和欄位識別符可以使用反引號 ( `
) 引起來。或者,如果啟用 ANSI_QUOTES
SQL 模式,則可以使用雙引號 ( "
)。也會考量 lower_case_table_names
系統變數設定。
外鍵約束受以下條件和限制約束
父資料表和子資料表必須使用相同的儲存引擎,並且不能定義為臨時資料表。
建立外鍵約束需要父資料表的
REFERENCES
權限。外鍵中的對應欄位和被參照的鍵必須具有相似的資料類型。固定精確度類型 (例如
INTEGER
和DECIMAL
) 的大小和符號必須相同。字串類型的長度不必相同。對於非二進位 (字元) 字串欄位,字元集和定序必須相同。MySQL 支援資料表內一個欄位與另一個欄位之間的外鍵參照。(一個欄位不能有對其自身的外鍵參照。)在這些情況下,「子資料表記錄」是指同一個資料表中從屬的記錄。
MySQL 要求外鍵和被參照的鍵具有索引,以便外鍵檢查可以快速執行,而不需要資料表掃描。在參照資料表中,必須有一個索引,其中外鍵欄位以相同的順序列為第一個欄位。如果不存在,則會在參照資料表中自動建立此類索引。如果您建立另一個可以用來強制執行外鍵約束的索引,則此索引可能會在稍後被靜默刪除。如果指定,則會如先前所述使用
index_name
。先前,
InnoDB
允許外鍵參照任何索引欄位或欄位群組,即使是非唯一索引或部分索引,這是標準 SQL 的延伸。為了向後相容,這仍然被允許,但現在已被棄用;此外,必須透過設定restrict_fk_on_non_standard_key
來啟用。如果這樣做,則在被參照的資料表中仍然必須有一個索引,其中被參照的欄位以相同的順序作為第一個欄位。InnoDB
新增到索引的隱藏欄位也會在此類情況下被考量(請參閱第 17.6.2.1 節,「叢集索引和次要索引」)。您應該預期 MySQL 的未來版本會移除對使用非標準鍵的支援,並從其使用中遷移。NDB
始終要求在任何被參照為外鍵的欄位上使用明確的唯一鍵 (或主鍵)。InnoDB
目前不支援具有使用者定義分割的資料表的外鍵。這包括父資料表和子資料表。此限制不適用於由
KEY
或LINEAR KEY
分割的NDB
資料表 (這是NDB
儲存引擎支援的唯一使用者分割類型);這些資料表可能有外鍵參照,或是此類參照的目標。外鍵關係中的資料表不能變更為使用另一個儲存引擎。若要變更儲存引擎,您必須先刪除所有外鍵約束。
外鍵約束不能參照虛擬產生的欄位。
如需有關 MySQL 外鍵約束實作與 SQL 標準的差異的資訊,請參閱第 1.7.2.3 節,「FOREIGN KEY 約束差異」。
當 UPDATE
或 DELETE
作業影響父資料表中的鍵值時,如果子資料表中有相符的列,則結果取決於 FOREIGN KEY
子句的 ON UPDATE
和 ON DELETE
子句指定的參照動作。參照動作包括
CASCADE
:從父資料表中刪除或更新列,並自動刪除或更新子資料表中的相符列。支援ON DELETE CASCADE
和ON UPDATE CASCADE
。在兩個資料表之間,不要定義多個對父資料表或子資料表中的相同欄位起作用的ON UPDATE CASCADE
子句。如果在外鍵關係中的兩個資料表上都定義了
FOREIGN KEY
子句,使兩個資料表同時成為父資料表和子資料表,則必須為另一個資料表定義為一個FOREIGN KEY
子句定義的ON UPDATE CASCADE
或ON DELETE CASCADE
子句,才能使串聯作業成功。如果僅為一個FOREIGN KEY
子句定義了ON UPDATE CASCADE
或ON DELETE CASCADE
子句,則串聯作業會失敗並出現錯誤。注意串聯的外鍵動作不會啟用觸發程序。
SET NULL
:從父資料表中刪除或更新列,並將子資料表中的外鍵欄位或欄位設定為NULL
。支援ON DELETE SET NULL
和ON UPDATE SET NULL
子句。如果您指定
SET NULL
動作,請確保您沒有將子資料表中的欄位宣告為NOT NULL
。RESTRICT
:拒絕父資料表的刪除或更新作業。指定RESTRICT
(或NO ACTION
) 與省略ON DELETE
或ON UPDATE
子句相同。NO ACTION
:來自標準 SQL 的關鍵字。對於InnoDB
,這相當於RESTRICT
;如果被參照的資料表中有相關的外鍵值,則會立即拒絕父資料表的刪除或更新作業。NDB
支援延遲檢查,而NO ACTION
指定延遲檢查;使用此設定時,直到提交時才會執行約束檢查。請注意,對於NDB
資料表,這會導致為父資料表和子資料表執行的所有外鍵檢查都被延遲。SET DEFAULT
:這個動作可以被 MySQL 解析器識別,但是InnoDB
和NDB
都會拒絕包含ON DELETE SET DEFAULT
或ON UPDATE SET DEFAULT
子句的資料表定義。
對於支援外鍵的儲存引擎,如果子資料表中嘗試建立的外鍵值在父資料表中沒有匹配的候選鍵值,MySQL 會拒絕任何 INSERT
或 UPDATE
操作。
對於未指定的 ON DELETE
或 ON UPDATE
,預設動作始終為 NO ACTION
。
作為預設值,明確指定的 ON DELETE NO ACTION
或 ON UPDATE NO ACTION
子句不會出現在 SHOW CREATE TABLE
輸出中,也不會出現在使用 mysqldump 傾印的資料表中。RESTRICT
是一個等效的非預設關鍵字,它會出現在 SHOW CREATE TABLE
輸出中,也會出現在使用 mysqldump 傾印的資料表中。
對於 NDB
資料表,當參考對象是父資料表的主鍵時,不支援 ON UPDATE CASCADE
。
對於 NDB
資料表,當子資料表包含一或多個 TEXT
或 BLOB
類型欄位時,不支援 ON DELETE CASCADE
。(錯誤 #89511, 錯誤 #27484882)
InnoDB
使用深度優先搜尋演算法,對應於外鍵約束的索引記錄執行級聯操作。
儲存的產生欄位上的外鍵約束不能使用 CASCADE
、SET NULL
或 SET DEFAULT
作為 ON UPDATE
參照動作,也不能使用 SET NULL
或 SET DEFAULT
作為 ON DELETE
參照動作。
儲存的產生欄位的基礎欄位上的外鍵約束不能使用 CASCADE
、SET NULL
或 SET DEFAULT
作為 ON UPDATE
或 ON DELETE
參照動作。
這個簡單的範例透過單一欄位外鍵關聯 parent
和 child
資料表
CREATE TABLE parent (
id INT NOT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB;
CREATE TABLE child (
id INT,
parent_id INT,
INDEX par_ind (parent_id),
FOREIGN KEY (parent_id)
REFERENCES parent(id)
ON DELETE CASCADE
) ENGINE=INNODB;
這是一個更複雜的範例,其中 product_order
資料表有另外兩個資料表的外鍵。其中一個外鍵參照 product
資料表中一個兩欄位的索引。另一個外鍵參照 customer
資料表中一個單一欄位的索引
CREATE TABLE product (
category INT NOT NULL, id INT NOT NULL,
price DECIMAL,
PRIMARY KEY(category, id)
) ENGINE=INNODB;
CREATE TABLE customer (
id INT NOT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB;
CREATE TABLE product_order (
no INT NOT NULL AUTO_INCREMENT,
product_category INT NOT NULL,
product_id INT NOT NULL,
customer_id INT NOT NULL,
PRIMARY KEY(no),
INDEX (product_category, product_id),
INDEX (customer_id),
FOREIGN KEY (product_category, product_id)
REFERENCES product(category, id)
ON UPDATE CASCADE ON DELETE RESTRICT,
FOREIGN KEY (customer_id)
REFERENCES customer(id)
) ENGINE=INNODB;
您可以使用以下 ALTER TABLE
語法,將外鍵約束新增至現有的資料表
ALTER TABLE tbl_name
ADD [CONSTRAINT [symbol]] FOREIGN KEY
[index_name] (col_name, ...)
REFERENCES tbl_name (col_name,...)
[ON DELETE reference_option]
[ON UPDATE reference_option]
外鍵可以是自我參照 (參照同一個資料表)。當您使用 ALTER TABLE
將外鍵約束新增至資料表時,請記得先在以外鍵參照的欄位上建立索引。
您可以使用以下 ALTER TABLE
語法捨棄外鍵約束
ALTER TABLE tbl_name DROP FOREIGN KEY fk_symbol;
如果您在建立約束時,FOREIGN KEY
子句定義了 CONSTRAINT
名稱,您可以使用該名稱來捨棄外鍵約束。否則,會自動產生一個約束名稱,您必須使用該值。若要判斷外鍵約束名稱,請使用 SHOW CREATE TABLE
mysql> SHOW CREATE TABLE child\G
*************************** 1. row ***************************
Table: child
Create Table: CREATE TABLE `child` (
`id` int DEFAULT NULL,
`parent_id` int DEFAULT NULL,
KEY `par_ind` (`parent_id`),
CONSTRAINT `child_ibfk_1` FOREIGN KEY (`parent_id`)
REFERENCES `parent` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
mysql> ALTER TABLE child DROP FOREIGN KEY `child_ibfk_1`;
在同一個 ALTER TABLE
陳述式中新增和捨棄外鍵,支援 ALTER TABLE ... ALGORITHM=INPLACE
。不支援 ALTER TABLE ... ALGORITHM=COPY
。
在 MySQL 中,InnoDB 和 NDB 資料表支援外鍵約束的檢查。外鍵檢查是由 foreign_key_checks
變數控制,預設為啟用。通常,您會在正常操作期間保持此變數啟用,以強制執行參照完整性。foreign_key_checks
變數對於 NDB
資料表的作用與對 InnoDB
資料表的作用相同。
foreign_key_checks
變數是動態的,並支援全域和工作階段範圍。如需使用系統變數的相關資訊,請參閱第 7.1.9 節「使用系統變數」。
在以下情況下,停用外鍵檢查很有用
捨棄由外鍵約束參照的資料表。只有在停用
foreign_key_checks
後,才能捨棄參照的資料表。當您捨棄資料表時,也會捨棄在資料表上定義的約束。以與外鍵關聯要求的順序不同的順序重新載入資料表。例如,mysqldump 會在傾印檔案中產生資料表的正確定義,包括子資料表的外鍵約束。為了更容易重新載入具有外鍵關聯的資料表的傾印檔案,mysqldump 會自動在傾印輸出中包含一個停用
foreign_key_checks
的陳述式。這使您可以在傾印檔案包含未針對外鍵正確排序的資料表時,以任何順序匯入資料表。停用foreign_key_checks
也會透過避免外鍵檢查來加速匯入操作。執行
LOAD DATA
操作以避免外鍵檢查。對具有外鍵關聯的資料表執行
ALTER TABLE
操作。
當 foreign_key_checks
停用時,會忽略外鍵約束,但以下例外情況除外
如果資料表定義不符合參照該資料表的外鍵約束,則重新建立先前已捨棄的資料表會傳回錯誤。資料表必須具有正確的欄位名稱和類型。它也必須在參照的鍵上具有索引。如果未滿足這些需求,MySQL 會傳回錯誤 1005,該錯誤訊息中會參照 errno: 150,這表示外鍵約束未正確形成。
如果針對變更後的資料表錯誤形成外鍵定義,則變更資料表會傳回錯誤 (errno: 150)。
捨棄外鍵約束所需的索引。必須先移除外鍵約束,才能捨棄索引。
建立外鍵約束,其中欄位參照不符合的欄位類型。
停用 foreign_key_checks
還具有以下其他含義
允許捨棄包含外鍵的資料庫,這些外鍵由資料庫外的資料表參照。
允許捨棄具有由其他資料表參照的外鍵的資料表。
啟用
foreign_key_checks
不會觸發資料表資料的掃描,這表示當foreign_key_checks
停用時新增至資料表的資料列,在重新啟用foreign_key_checks
時不會檢查一致性。
MySQL 會根據需要將中繼資料鎖延伸至透過外鍵約束相關聯的資料表。延伸中繼資料鎖可防止相關資料表上同時執行衝突的 DML 和 DDL 操作。此功能還可在修改父資料表時,更新外鍵中繼資料。在較早的 MySQL 版本中,由子資料表擁有的外鍵中繼資料無法安全地更新。
如果使用 LOCK TABLES
明確鎖定資料表,則會隱含地開啟和鎖定透過外鍵約束相關聯的任何資料表。對於外鍵檢查,會對相關資料表進行共用唯讀鎖定 (LOCK TABLES READ
)。對於級聯更新,會對操作中涉及的相關資料表進行共用無寫入鎖定 (LOCK TABLES WRITE
)。
若要檢視外鍵定義,請使用 SHOW CREATE TABLE
mysql> SHOW CREATE TABLE child\G
*************************** 1. row ***************************
Table: child
Create Table: CREATE TABLE `child` (
`id` int DEFAULT NULL,
`parent_id` int DEFAULT NULL,
KEY `par_ind` (`parent_id`),
CONSTRAINT `child_ibfk_1` FOREIGN KEY (`parent_id`)
REFERENCES `parent` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
您可以從 Information Schema KEY_COLUMN_USAGE
資料表取得外鍵的相關資訊。以下顯示針對此資料表查詢的範例
mysql> SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE REFERENCED_TABLE_SCHEMA IS NOT NULL;
+--------------+------------+-------------+-----------------+
| TABLE_SCHEMA | TABLE_NAME | COLUMN_NAME | CONSTRAINT_NAME |
+--------------+------------+-------------+-----------------+
| test | child | parent_id | child_ibfk_1 |
+--------------+------------+-------------+-----------------+
您可以從 INNODB_FOREIGN
和 INNODB_FOREIGN_COLS
資料表取得特定於 InnoDB
外鍵的資訊。以下顯示查詢範例
mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_FOREIGN \G
*************************** 1. row ***************************
ID: test/child_ibfk_1
FOR_NAME: test/child
REF_NAME: test/parent
N_COLS: 1
TYPE: 1
mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_FOREIGN_COLS \G
*************************** 1. row ***************************
ID: test/child_ibfk_1
FOR_COL_NAME: parent_id
REF_COL_NAME: id
POS: 0
如果發生與 InnoDB
資料表相關的外鍵錯誤 (通常是 MySQL Server 中的錯誤 150),可以透過檢查 SHOW ENGINE INNODB STATUS
輸出來取得有關最新外鍵錯誤的資訊。
mysql> SHOW ENGINE INNODB STATUS\G
...
------------------------
LATEST FOREIGN KEY ERROR
------------------------
2018-04-12 14:57:24 0x7f97a9c91700 Transaction:
TRANSACTION 7717, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1136, 3 row lock(s), undo log entries 3
MySQL thread id 8, OS thread handle 140289365317376, query id 14 localhost root update
INSERT INTO child VALUES (NULL, 1), (NULL, 2), (NULL, 3), (NULL, 4), (NULL, 5), (NULL, 6)
Foreign key constraint fails for table `test`.`child`:
,
CONSTRAINT `child_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`id`) ON DELETE
CASCADE ON UPDATE CASCADE
Trying to add in child table, in index par_ind tuple:
DATA TUPLE: 2 fields;
0: len 4; hex 80000003; asc ;;
1: len 4; hex 80000003; asc ;;
But in parent table `test`.`parent`, in index PRIMARY,
the closest match we can find is record:
PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 80000004; asc ;;
1: len 6; hex 000000001e19; asc ;;
2: len 7; hex 81000001110137; asc 7;;
...
如果使用者擁有所有父資料表的表格層級權限,針對外鍵操作,ER_NO_REFERENCED_ROW_2
和 ER_ROW_IS_REFERENCED_2
錯誤訊息會洩漏有關父資料表的資訊。如果使用者沒有所有父資料表的表格層級權限,則會顯示更通用的錯誤訊息(ER_NO_REFERENCED_ROW
和 ER_ROW_IS_REFERENCED
)。
有一個例外情況是,對於定義為使用 DEFINER
權限執行的預存程式,權限評估的依據是用於程式 DEFINER
子句中的使用者,而不是調用的使用者。如果該使用者擁有父資料表的表格層級權限,則仍然會顯示父資料表的資訊。在這種情況下,預存程式的建立者有責任透過包含適當的條件處理常式來隱藏資訊。