MySQL 支持外键,允许跨表交叉引用相关数据,以及外键约束,帮助保持相关数据的一致性。
外键关系涉及一个父表,该表包含初始列值,以及一个子表,该表的列值引用父表的列值。外键约束是在子表上定义的。
在 CREATE TABLE
或 ALTER 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
关键字后未包含符号,InnoDB
和NDB
存储引擎都会忽略FOREIGN_KEY
。index_name
如果定义了
CONSTRAINT
值,则该值必须在数据库中唯一。重复的symbol
symbol
会导致类似以下错误:ERROR 1005 (HY000): Can't create table 'test.fk1' (errno: 121)。NDB Cluster 使用创建时相同的字母大小写存储外键名称。
外键 FOREIGN KEY ... REFERENCES
子句中的表和列标识符可以用反引号 (`
) 括起来。或者,如果启用了 ANSI_QUOTES
SQL 模式,则可以使用双引号 ("
)。lower_case_table_names
系统变量设置也会被考虑在内。
外键约束受以下条件和限制的约束
父表和子表必须使用相同的存储引擎,并且不能定义为临时表。
创建外键约束需要对父表具有
REFERENCES
权限。外键和被引用键中的对应列必须具有类似的数据类型。
INTEGER
和DECIMAL
等固定精度类型的尺寸和符号必须相同。字符串类型的长度不必相同。对于非二进制(字符)字符串列,字符集和排序规则必须相同。MySQL 支持在一个表内的两个列之间的外键引用。(列不能对自身进行外键引用。)在这些情况下,“子表记录” 指的是同一个表中的相关记录。
MySQL 要求对外键和被引用键进行索引,以便外键检查能够快速进行,并且不需要表扫描。在引用表中,必须有一个索引,其中外键列作为第一个列按相同顺序列出。如果不存在,则会在引用表上自动创建此类索引。如果您创建了另一个可用于强制实施外键约束的索引,则此索引可能会在稍后被静默删除。
index_name
,如果给出,则按前述方式使用。以前,
InnoDB
允许外键引用任何索引列或列组,即使是非唯一索引或部分索引,这也是标准 SQL 的扩展。为了向后兼容,这仍然允许,但现在已弃用;此外,必须通过设置restrict_fk_on_non_standard_key
来启用它。如果这样做,被引用表中仍然必须有一个索引,其中被引用列是第一个列,按相同顺序排列。InnoDB
添加到索引的隐藏列也会在这些情况下被考虑(参见 第 17.6.2.1 节,“聚簇索引和二级索引”)。您应该预期在 MySQL 的未来版本中删除对使用非标准键的支持,并迁移到不再使用它们。NDB
始终要求在作为外键引用的任何列上有一个显式唯一键(或主键)。InnoDB
目前不支持对具有用户定义分区表的表使用外键。这包括父表和子表。此限制不适用于通过
KEY
或LINEAR KEY
分区的NDB
表(NDB
存储引擎支持的唯一用户分区类型);这些表可以有外键引用,也可以是此类引用的目标。外键关系中的表不能更改为使用其他存储引擎。要更改存储引擎,必须先删除所有外键约束。
外键约束不能引用虚拟生成列。
有关 MySQL 中外键约束实现与 SQL 标准的不同之处的信息,请参见 第 1.7.2.3 节,“FOREIGN KEY 约束差异”。
当 UPDATE
或 DELETE
操作影响父表中具有与子表中的匹配行相匹配的键值时,结果取决于由 FOREIGN KEY
子句的 ON UPDATE
和 ON DELETE
子句指定的引用操作。引用操作包括
CASCADE
:从父表中删除或更新行,并自动删除或更新子表中的匹配行。支持ON DELETE CASCADE
和ON UPDATE CASCADE
。在两个表之间,不要定义多个针对父表或子表中的同一列执行操作的ON UPDATE CASCADE
子句。如果在外键关系中的两个表上都定义了
FOREIGN KEY
子句,使得两个表都成为父表和子表,则必须为一个FOREIGN KEY
子句定义的ON UPDATE CASCADE
或ON DELETE CASCADE
子句也必须为另一个FOREIGN KEY
子句定义,才能使级联操作成功。如果仅为一个FOREIGN KEY
子句定义了ON UPDATE CASCADE
或ON DELETE CASCADE
子句,则级联操作将失败并出现错误。注意级联外键操作不会激活触发器。
SET NULL
:从父表中删除或更新行,并将子表中的外键列或列设置为NULL
。支持ON DELETE SET NULL
和ON UPDATE SET NULL
子句。如果指定了
SET NULL
操作,请确保您没有将子表中的列声明为NOT NULL
。RESTRICT
:拒绝父表的删除或更新操作。指定RESTRICT
(或NO ACTION
)与省略ON DELETE
或ON UPDATE
子句相同。NO ACTION
:标准 SQL 中的关键字。对于InnoDB
,这等效于RESTRICT
;如果被引用表中存在相关外键值,则立即拒绝父表的删除或更新操作。NDB
支持延迟检查,NO ACTION
指定延迟检查;使用它时,约束检查不会在提交时执行。请注意,对于NDB
表,这会导致对父表和子表执行的所有外键检查都被延迟。SET DEFAULT
:此操作被 MySQL 解析器识别,但InnoDB
和NDB
都拒绝包含ON DELETE SET DEFAULT
或ON UPDATE SET DEFAULT
子句的表定义。
对于支持外键的存储引擎,MySQL 会拒绝任何试图在子表中创建外键值而父表中没有匹配的候选键值的 INSERT
或 UPDATE
操作。
对于未指定的 ON DELETE
或 ON UPDATE
,默认操作始终为 NO ACTION
。
默认情况下,显式指定的 ON DELETE NO ACTION
或 ON UPDATE NO ACTION
子句不会出现在 SHOW CREATE TABLE
输出中,也不会出现在使用 mysqldump 转储的表中。RESTRICT
,它是一个等效的非默认关键字,会出现在 SHOW CREATE TABLE
输出中,也会出现在使用 mysqldump 转储的表中。
对于 NDB
表,不支持 ON UPDATE CASCADE
,其中引用指向父表的主键。
对于 NDB
表,不支持 ON DELETE CASCADE
,其中子表包含一个或多个任何 TEXT
或 BLOB
类型的列。(错误 #89511,错误 #27484882)
InnoDB
使用深度优先搜索算法对与外键约束相对应的索引的记录执行级联操作。
存储生成列上的外键约束不能使用 CASCADE
、SET NULL
或 SET DEFAULT
作为 ON UPDATE
引用操作,也不能使用 SET NULL
或 SET DEFAULT
作为 ON DELETE
引用操作。
存储生成列的基列上的外键约束不能使用 CASCADE
、SET NULL
或 SET DEFAULT
作为 ON UPDATE
或 ON DELETE
引用操作。
此简单示例通过单列外键将 parent
和 child
表关联起来
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
语句中添加和删除外键,对于 ALTER TABLE ... ALGORITHM=INPLACE
是受支持的。对于 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
您可以从 Information Schema 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_FOREIGN
和 INNODB_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 服务器中的错误 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_2
和 ER_ROW_IS_REFERENCED_2
外键操作的错误消息会公开有关父表的信息。如果用户没有对所有父表具有表级权限,则会显示更通用的错误消息(ER_NO_REFERENCED_ROW
和 ER_ROW_IS_REFERENCED
)。
一个例外是,对于定义为使用 DEFINER
权限执行的存储程序,评估权限的用户是程序 DEFINER
子句中的用户,而不是调用用户。如果该用户具有表级父表权限,则仍会显示父表信息。在这种情况下,存储程序创建者有责任通过包含适当的条件处理程序来隐藏信息。