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 的未来版本中会删除对非标准键使用的支持,现在就迁移到其他方法。

    根据 SQL 标准,NDB 存储引擎要求对任何引用为外键的列设置显式唯一键(或主键)。

  • 对于不支持外键的存储引擎(如 MyISAM),MySQL 服务器会解析并忽略外键规范。

  • MySQL 会解析但忽略 内联 REFERENCES 规范(如 SQL 标准中定义的),其中引用被定义为列规范的一部分。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 节,“外键约束”