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 CASCADE
或ON UPDATE SET NULL
,并且在同一个级别中已经更新过了该表,它就像RESTRICT
一样。因此,你不能使用自引用ON UPDATE CASCADE
或ON UPDATE SET NULL
操作。这是为了防止级联更新导致的无限循环。反之,自引用ON DELETE SET NULL
却是可能的,同样也可以使用自引用ON DELETE CASCADE
。级联操作最多只能15层深。 -
在插入、删除或更新多行的SQL语句中,外键约束(如唯一约束)是逐行检查的。在执行外键检查时,
InnoDB
对子记录或父记录设置共享行锁。MySQL立即检查外键约束;检查不被延迟到事务提交。根据SQL标准,缺省行为应该是延迟检查。这意味着不能使用外键删除指向自己的一行记录。 -
包括
InnoDB
在内的所有存储引擎都不会识别或强制执行referential-integrity约束定义中的MATCH
子句。使用明确的MATCH
子句不起预期效果,并且会忽略ON DELETE
和ON UPDATE
子句。指定MATCH
应该被避免。SQL标准中的
MATCH
子句控制多列外键中NULL
值在与参考表的主键比较时的处理方式。MySQL实际实现了MATCH SIMPLE
语义,允许外键完全或部分为NULL
。因此,即使该行不匹配任何记录,也可以插入子表(child table)。(使用触发器也可以实现其他语义。) -
MySQL 需要引用列被索引,以提高性能。但是,MySQL 不强制要求引用列为
UNIQUE
或声明为NOT NULL
FOREIGN KEY
约束引用非唯一键不是标准SQL,而是InnoDB
的扩展。反之,NDB
存储引擎则要求对任何被引用为外键的列必须有明确的唯一键(或主键)。非唯一键或包含
NULL
值的外键引用在操作,如UPDATE
或DELETE 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 DELETE
或ON UPDATE
行为(虽然你可以在REFERENCES
子句中写入ON DELETE
或ON 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 TABLE
或DESCRIBE
的输出结果中显示: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节,“外键约束”。