MySQL 支持外键,允许跨表交叉引用相关数据,以及外键约束,有助于保持相关数据的 consistency。
外键关系涉及一个父表,它包含初始列值,以及一个子表,其列值引用父表列值。外键约束在子表上定义。
在 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
当前不支持具有用户定义分区表的表的外键。这包括父表和子表。此限制不适用于
NDB
表,这些表通过KEY
或LINEAR KEY
(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
子句在另一个子句中定义,才能使级联操作成功。如果ON UPDATE CASCADE
或ON DELETE CASCADE
子句仅为一个FOREIGN KEY
子句定义,则级联操作会失败并出现错误。注意级联外键操作不会激活触发器。
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;
这是一个更复杂的示例,其中 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 DEFAULT 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 DEFAULT 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
子句中的用户,而不是调用用户。如果该用户具有表级父表权限,则仍会显示父表信息。在这种情况下,存储程序创建者有责任通过包含适当的条件处理程序来隐藏信息。