文件首頁
MySQL 8.4 參考手冊
相關文件 下載本手冊
PDF (美式信紙尺寸) - 39.9Mb
PDF (A4 尺寸) - 40.0Mb
Man Pages (TGZ) - 258.5Kb
Man Pages (Zip) - 365.5Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 8.4 參考手冊  /  ...  /  FOREIGN KEY 約束

15.1.20.5 FOREIGN KEY 約束

MySQL 支援外鍵,允許跨資料表交叉參照相關資料,以及外鍵約束,有助於保持相關資料的一致性。

外鍵關係涉及一個持有初始欄位值的父資料表,以及一個欄位值參照父資料表欄位值的子資料表。外鍵約束定義在子資料表上。

CREATE TABLEALTER 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 關鍵字後未包含符號,則 InnoDBNDB 儲存引擎都會忽略 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 權限。

  • 外鍵中的對應欄位和被參照的鍵必須具有相似的資料類型。固定精確度類型 (例如 INTEGERDECIMAL) 的大小和符號必須相同。字串類型的長度不必相同。對於非二進位 (字元) 字串欄位,字元集和定序必須相同。

  • MySQL 支援資料表內一個欄位與另一個欄位之間的外鍵參照。(一個欄位不能有對其自身的外鍵參照。)在這些情況下,「子資料表記錄」是指同一個資料表中從屬的記錄。

  • MySQL 要求外鍵和被參照的鍵具有索引,以便外鍵檢查可以快速執行,而不需要資料表掃描。在參照資料表中,必須有一個索引,其中外鍵欄位以相同的順序列為第一個欄位。如果不存在,則會在參照資料表中自動建立此類索引。如果您建立另一個可以用來強制執行外鍵約束的索引,則此索引可能會在稍後被靜默刪除。如果指定,則會如先前所述使用 index_name

  • 先前,InnoDB 允許外鍵參照任何索引欄位或欄位群組,即使是非唯一索引或部分索引,這是標準 SQL 的延伸。為了向後相容,這仍然被允許,但現在已被棄用;此外,必須透過設定 restrict_fk_on_non_standard_key 來啟用。如果這樣做,則在被參照的資料表中仍然必須有一個索引,其中被參照的欄位以相同的順序作為第一個欄位。InnoDB 新增到索引的隱藏欄位也會在此類情況下被考量(請參閱第 17.6.2.1 節,「叢集索引和次要索引」)。您應該預期 MySQL 的未來版本會移除對使用非標準鍵的支援,並從其使用中遷移。

    NDB 始終要求在任何被參照為外鍵的欄位上使用明確的唯一鍵 (或主鍵)。

  • 不支援外鍵欄位的索引首碼。因此,BLOBTEXT 欄位不能包含在外鍵中,因為這些欄位的索引必須始終包含首碼長度。

  • InnoDB 目前不支援具有使用者定義分割的資料表的外鍵。這包括父資料表和子資料表。

    此限制不適用於由 KEYLINEAR KEY 分割的 NDB 資料表 (這是 NDB 儲存引擎支援的唯一使用者分割類型);這些資料表可能有外鍵參照,或是此類參照的目標。

  • 外鍵關係中的資料表不能變更為使用另一個儲存引擎。若要變更儲存引擎,您必須先刪除所有外鍵約束。

  • 外鍵約束不能參照虛擬產生的欄位。

如需有關 MySQL 外鍵約束實作與 SQL 標準的差異的資訊,請參閱第 1.7.2.3 節,「FOREIGN KEY 約束差異」

參照動作

UPDATEDELETE 作業影響父資料表中的鍵值時,如果子資料表中有相符的列,則結果取決於 FOREIGN KEY 子句的 ON UPDATEON DELETE 子句指定的參照動作。參照動作包括

  • CASCADE:從父資料表中刪除或更新列,並自動刪除或更新子資料表中的相符列。支援 ON DELETE CASCADEON UPDATE CASCADE。在兩個資料表之間,不要定義多個對父資料表或子資料表中的相同欄位起作用的 ON UPDATE CASCADE 子句。

    如果在外鍵關係中的兩個資料表上都定義了 FOREIGN KEY 子句,使兩個資料表同時成為父資料表和子資料表,則必須為另一個資料表定義為一個 FOREIGN KEY 子句定義的 ON UPDATE CASCADEON DELETE CASCADE 子句,才能使串聯作業成功。如果僅為一個 FOREIGN KEY 子句定義了 ON UPDATE CASCADEON DELETE CASCADE 子句,則串聯作業會失敗並出現錯誤。

    注意

    串聯的外鍵動作不會啟用觸發程序。

  • SET NULL:從父資料表中刪除或更新列,並將子資料表中的外鍵欄位或欄位設定為 NULL。支援 ON DELETE SET NULLON UPDATE SET NULL 子句。

    如果您指定 SET NULL 動作,請確保您沒有將子資料表中的欄位宣告為 NOT NULL

  • RESTRICT:拒絕父資料表的刪除或更新作業。指定 RESTRICT (或 NO ACTION) 與省略 ON DELETEON UPDATE 子句相同。

  • NO ACTION:來自標準 SQL 的關鍵字。對於 InnoDB,這相當於 RESTRICT;如果被參照的資料表中有相關的外鍵值,則會立即拒絕父資料表的刪除或更新作業。NDB 支援延遲檢查,而 NO ACTION 指定延遲檢查;使用此設定時,直到提交時才會執行約束檢查。請注意,對於 NDB 資料表,這會導致為父資料表和子資料表執行的所有外鍵檢查都被延遲。

  • SET DEFAULT:這個動作可以被 MySQL 解析器識別,但是 InnoDBNDB 都會拒絕包含 ON DELETE SET DEFAULTON UPDATE SET DEFAULT 子句的資料表定義。

對於支援外鍵的儲存引擎,如果子資料表中嘗試建立的外鍵值在父資料表中沒有匹配的候選鍵值,MySQL 會拒絕任何 INSERTUPDATE 操作。

對於未指定的 ON DELETEON UPDATE,預設動作始終為 NO ACTION

作為預設值,明確指定的 ON DELETE NO ACTIONON UPDATE NO ACTION 子句不會出現在 SHOW CREATE TABLE 輸出中,也不會出現在使用 mysqldump 傾印的資料表中。RESTRICT 是一個等效的非預設關鍵字,它會出現在 SHOW CREATE TABLE 輸出中,也會出現在使用 mysqldump 傾印的資料表中。

對於 NDB 資料表,當參考對象是父資料表的主鍵時,不支援 ON UPDATE CASCADE

對於 NDB 資料表,當子資料表包含一或多個 TEXTBLOB 類型欄位時,不支援 ON DELETE CASCADE。(錯誤 #89511, 錯誤 #27484882)

InnoDB 使用深度優先搜尋演算法,對應於外鍵約束的索引記錄執行級聯操作。

儲存的產生欄位上的外鍵約束不能使用 CASCADESET NULLSET DEFAULT 作為 ON UPDATE 參照動作,也不能使用 SET NULLSET DEFAULT 作為 ON DELETE 參照動作。

儲存的產生欄位的基礎欄位上的外鍵約束不能使用 CASCADESET NULLSET DEFAULT 作為 ON UPDATEON DELETE 參照動作。

外鍵約束範例

這個簡單的範例透過單一欄位外鍵關聯 parentchild 資料表

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_FOREIGNINNODB_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_2ER_ROW_IS_REFERENCED_2 錯誤訊息會洩漏有關父資料表的資訊。如果使用者沒有所有父資料表的表格層級權限,則會顯示更通用的錯誤訊息(ER_NO_REFERENCED_ROWER_ROW_IS_REFERENCED)。

有一個例外情況是,對於定義為使用 DEFINER 權限執行的預存程式,權限評估的依據是用於程式 DEFINER 子句中的使用者,而不是調用的使用者。如果該使用者擁有父資料表的表格層級權限,則仍然會顯示父資料表的資訊。在這種情況下,預存程式的建立者有責任透過包含適當的條件處理常式來隱藏資訊。