1.7.2.3 外键约束差异

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标准,缺省行为应该是延迟检查。这意味着不能使用外键删除指向自己的一行记录。

  • 包括InnoDB在内的所有存储引擎都不会识别或强制执行referential-integrity约束定义中的MATCH子句。使用明确的MATCH子句不起预期效果,并且会忽略ON DELETEON UPDATE子句。指定MATCH应该被避免。

    SQL标准中的MATCH子句控制多列外键中NULL值在与参考表的主键比较时的处理方式。MySQL实际实现了MATCH SIMPLE语义,允许外键完全或部分为NULL。因此,即使该行不匹配任何记录,也可以插入子表(child table)。(使用触发器也可以实现其他语义。)

  • MySQL 需要引用列被索引,以提高性能。但是,MySQL 不强制要求引用列为UNIQUE或声明为NOT NULL

    FOREIGN KEY 约束引用非唯一键不是标准SQL,而是InnoDB 的扩展。反之,NDB 存储引擎则要求对任何被引用为外键的列必须有明确的唯一键(或主键)。

    非唯一键或包含NULL 值的外键引用在操作,如UPDATEDELETE CASCADE,其处理方式不明确。因此,建议使用只引用唯一(包括主键)和NOT NULL 键的外键。

  • 对于不支持外键的存储引擎(如MyISAM),MySQL 服务器将忽略外键约束。

  • MySQL 解析但忽略了inline REFERENCES specifications(按照 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节,“外键约束”