文件首頁
MySQL 9.0 參考手冊
相關文件 下載本手冊
PDF (US Ltr) - 40.0Mb
PDF (A4) - 40.1Mb
Man Pages (TGZ) - 258.2Kb
Man Pages (Zip) - 365.3Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 9.0 參考手冊  /  ...  /  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' (errno: 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 節,「外鍵約束差異」

參考動作

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;

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_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 子句中的使用者,而不是呼叫的使用者。如果該使用者擁有父資料表的資料表層級權限,則仍會顯示父資料表的資訊。在這種情況下,預存程式的建立者有責任透過包含適當的條件處理常式來隱藏這些資訊。