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

1.7.2.3 FOREIGN KEY 約束差異

MySQL 的外鍵約束實作在以下幾個主要方面與 SQL 標準不同:

  • 如果父資料表中有數個具有相同參考鍵值的列,InnoDB 執行外鍵檢查時,會假設其他具有相同鍵值的父列不存在。例如,如果您定義 RESTRICT 類型的約束,並且有一個子列與多個父列相關聯,則 InnoDB 不允許刪除任何父列。以下範例說明了這一點:

    mysql> CREATE TABLE parent (
        ->     id INT,
        ->     INDEX (id)
        -> ) ENGINE=InnoDB;
    Query OK, 0 rows affected (0.04 sec)
    
    mysql> CREATE TABLE child (
        ->     id INT,
        ->     parent_id INT,
        ->     INDEX par_ind (parent_id),
        ->     FOREIGN KEY (parent_id)
        ->         REFERENCES parent(id)
        ->         ON DELETE RESTRICT
        -> ) ENGINE=InnoDB;
    Query OK, 0 rows affected (0.02 sec)
    
    mysql> INSERT INTO parent (id) 
        ->     VALUES ROW(1), ROW(2), ROW(3), ROW(1);
    Query OK, 4 rows affected (0.01 sec)
    Records: 4  Duplicates: 0  Warnings: 0
    
    mysql> INSERT INTO child (id,parent_id) 
        ->     VALUES ROW(1,1), ROW(2,2), ROW(3,3);
    Query OK, 3 rows affected (0.01 sec)
    Records: 3  Duplicates: 0  Warnings: 0
    
    mysql> DELETE FROM parent WHERE id=1;
    ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key
    constraint fails (`test`.`child`, CONSTRAINT `child_ibfk_1` FOREIGN KEY
    (`parent_id`) REFERENCES `parent` (`id`) ON DELETE RESTRICT)
  • 如果 ON UPDATE CASCADEON UPDATE SET NULL 遞迴更新在相同串聯期間先前更新的相同資料表,它的行為會類似於 RESTRICT。這表示您無法使用自我參照的 ON UPDATE CASCADEON UPDATE SET NULL 作業。這是為了防止因串聯更新而導致無限迴圈。另一方面,自我參照的 ON DELETE SET NULL 是可行的,自我參照的 ON DELETE CASCADE 也是如此。串聯操作的巢狀深度不得超過 15 層。

  • 在插入、刪除或更新多個列的 SQL 語法中,外鍵約束(如同唯一約束)會逐列檢查。InnoDB 在執行外鍵檢查時,會在它必須檢查的子記錄或父記錄上設定共用列級鎖定。MySQL 會立即檢查外鍵約束;檢查不會延遲到交易提交。根據 SQL 標準,預設行為應該是延遲檢查。也就是說,約束只有在處理完整個 SQL 語法後才會進行檢查。這表示不可能使用外鍵刪除參考自身的列。

  • 沒有任何儲存引擎(包括 InnoDB)會辨識或強制執行參考完整性約束定義中使用的 MATCH 子句。使用明確的 MATCH 子句不會產生指定的效果,並且會導致 ON DELETEON UPDATE 子句被忽略。應該避免指定 MATCH

    SQL 標準中的 MATCH 子句控制當與參考資料表中的主鍵比較時,如何處理複合(多欄)外鍵中的 NULL 值。MySQL 本質上實作了 MATCH SIMPLE 定義的語意,允許外鍵為全部或部分 NULL。在這種情況下,即使包含此外鍵的(子資料表)列與參考(父)資料表中沒有任何列相符,也可以插入該列。(可以使用觸發器來實作其他語意。)

  • 參考非 UNIQUE 鍵的 FOREIGN KEY 約束不是標準 SQL,而是 InnoDB 擴充功能,現在已棄用,而且必須透過設定 restrict_fk_on_non_standard_key 來啟用。您應該預期在未來版本的 MySQL 中移除對使用非標準鍵的支援,並且現在就開始從這些鍵遷移。

    NDB 儲存引擎需要每個做為外鍵參考的欄位上都有明確的唯一鍵(或主鍵),這符合 SQL 標準。

  • 對於不支援外鍵的儲存引擎(例如 MyISAM),MySQL 伺服器會解析並忽略外鍵規格。

  • MySQL 會解析但忽略(如 SQL 標準中定義)在定義參考做為欄位規格一部分的「內嵌 REFERENCES 規格」。MySQL 僅接受指定為單獨 FOREIGN KEY 規格一部分的 REFERENCES 子句。

    定義欄位以使用 REFERENCES tbl_name(col_name) 子句沒有實際效果,而且僅作為提醒或註解,告訴您目前正在定義的欄位是要參考另一個資料表中的欄位。在使用此語法時,務必瞭解:

    • MySQL 不會執行任何類型的檢查,以確定 col_name 實際上是否存在於 tbl_name 中(甚至 tbl_name 本身是否存在)。

    • MySQL 不會對 tbl_name 執行任何類型的動作,例如在您正在定義的資料表中的列上執行動作時刪除列;換句話說,此語法不會產生任何 ON DELETEON UPDATE 行為。(雖然您可以在 REFERENCES 子句中撰寫 ON DELETEON UPDATE 子句,但也會被忽略。)

    • 此語法會建立一個欄位;它不會建立任何類型的索引或索引鍵。

    您可以將如此建立的欄位用作聯結欄位,如下所示:

    CREATE TABLE person (
        id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
        name CHAR(60) NOT NULL,
        PRIMARY KEY (id)
    );
    
    CREATE TABLE shirt (
        id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
        style ENUM('t-shirt', 'polo', 'dress') NOT NULL,
        color ENUM('red', 'blue', 'orange', 'white', 'black') NOT NULL,
        owner SMALLINT UNSIGNED NOT NULL REFERENCES person(id),
        PRIMARY KEY (id)
    );
    
    INSERT INTO person VALUES (NULL, 'Antonio Paz');
    
    SELECT @last := LAST_INSERT_ID();
    
    INSERT INTO shirt VALUES
        ROW(NULL, 'polo', 'blue', @last),
        ROW(NULL, 'dress', 'white', @last),
        ROW(NULL, 't-shirt', 'blue', @last);
    
    INSERT INTO person VALUES (NULL, 'Lilliana Angelovska');
    
    SELECT @last := LAST_INSERT_ID();
    
    INSERT INTO shirt VALUES
        ROW(NULL, 'dress', 'orange', @last),
        ROW(NULL, 'polo', 'red', @last),
        ROW(NULL, 'dress', 'blue', @last),
        ROW(NULL, 't-shirt', 'white', @last);
    
    SELECT * FROM person;
    +----+---------------------+
    | id | name                |
    +----+---------------------+
    |  1 | Antonio Paz         |
    |  2 | Lilliana Angelovska |
    +----+---------------------+
    
    SELECT * FROM shirt;
    +----+---------+--------+-------+
    | id | style   | color  | owner |
    +----+---------+--------+-------+
    |  1 | polo    | blue   |     1 |
    |  2 | dress   | white  |     1 |
    |  3 | t-shirt | blue   |     1 |
    |  4 | dress   | orange |     2 |
    |  5 | polo    | red    |     2 |
    |  6 | dress   | blue   |     2 |
    |  7 | t-shirt | white  |     2 |
    +----+---------+--------+-------+
    
    SELECT s.* FROM person p INNER JOIN shirt s
       ON s.owner = p.id
    WHERE p.name LIKE 'Lilliana%'
       AND s.color <> 'white';
    
    +----+-------+--------+-------+
    | id | style | color  | owner |
    +----+-------+--------+-------+
    |  4 | dress | orange |     2 |
    |  5 | polo  | red    |     2 |
    |  6 | dress | blue   |     2 |
    +----+-------+--------+-------+

    以這種方式使用時,REFERENCES 子句不會顯示在 SHOW CREATE TABLEDESCRIBE 的輸出中。

    mysql> SHOW CREATE TABLE shirt\G
    *************************** 1. row ***************************
    Table: shirt
    Create Table: CREATE TABLE `shirt` (
    `id` smallint(5) unsigned NOT NULL auto_increment,
    `style` enum('t-shirt','polo','dress') NOT NULL,
    `color` enum('red','blue','orange','white','black') NOT NULL,
    `owner` smallint(5) unsigned NOT NULL,
    PRIMARY KEY  (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

如需外鍵約束的資訊,請參閱 章節 15.1.20.5,「FOREIGN KEY 約束」