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' (errno: 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 節,「外鍵約束差異」。
當 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;
MySQL 9.0 支援行內 REFERENCE
子句以及隱含的父資料表主鍵,因此第二個 CREATE TABLE
陳述式可以改寫如下
CREATE TABLE child (
id INT,
parent_id INT NOT NULL REFERENCES parent ON DELETE CASCADE,
INDEX par_ind (parent_id)
) 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 NOT 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 ... ALGORITHM=INPLACE
,支援在相同的 ALTER TABLE
陳述式中新增和刪除外鍵。對於 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 NOT 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
您可以從資訊綱要 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
子句中的使用者,而不是呼叫的使用者。如果該使用者擁有父資料表的資料表層級權限,則仍會顯示父資料表的資訊。在這種情況下,預存程式的建立者有責任透過包含適當的條件處理常式來隱藏這些資訊。