Documentation Home
MySQL 8.3 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 40.8Mb
PDF (A4) - 40.9Mb
Man Pages (TGZ) - 294.0Kb
Man Pages (Zip) - 409.0Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb
Excerpts from this Manual

15.1.20.5 外键约束

MySQL 支持外键,它们允许跨表引用相关数据,并且外键约束有助于保持相关数据的一致性。

外键关系涉及到一个父表,它持有初始列值,以及一个子表,其中的列值引用父表的列值。外键约束是在子表上定义的。

CREATE TABLEALTER 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 符号 值,则使用该值。

  • 如果未定义 CONSTRAINT 符号 子句,或者在 CONSTRAINT 关键字后未包含符号,则自动生成约束名称。

    如果不定义CONSTRAINT 符号子句,或者在CONSTRAINT关键字后没有符号,both InnoDBNDB 存储引擎将忽略 FOREIGN_KEY 索引名称

  • 如果定义了 CONSTRAINT 符号 值,该值必须在数据库中是唯一的。重复的 符号 将导致类似于:错误 1005 (HY000): 无法创建表 'test.fk1' (errno: 121)

  • NDB 集群使用与创建时相同的字母大小写来存储外键名称。

FOREIGN KEY ... REFERENCES 子句中,表和列标识符可以用反引号 (`) 引用。或者,如果启用了 ANSI_QUOTES SQL 模式,可以使用双引号 (")。还将考虑 lower_case_table_names 系统变量设置。

限制和约束

外键约束受以下限制和约束:

  • 父表和子表必须使用相同的存储引擎,并且不能定义为临时表。

  • 创建外键约束需要在父表上拥有 REFERENCES 权限。

  • 外键和引用的键的对应列必须具有相似的数据类型。固定精度类型,如 INTEGERDECIMAL,必须具有相同的大小和符号。字符串类型的长度无需相同。对于非二进制(字符)字符串列,字符集和排序规则必须相同。

  • MySQL 支持在同一表中的一个列和另一个列之间的外键引用。(一列不能对自己具有外键引用。)在这些情况下,一个 子表记录 指的是同一表中的一个依赖记录。

  • MySQL 需要在外键和引用键上创建索引,以便快速执行外键检查,而不需要扫描整个表。在引用表中,必须在外键列上创建索引,这些列必须是索引的第一列,以相同的顺序。如果不存在这样的索引,MySQL 将自动创建一个。如果您创建了另一个可以用来执行外键约束的索引,可能会悄悄地删除该索引。index_name,如果给定,将按照之前描述的方式使用。

  • InnoDB 允许外键引用任何索引列或列组。但是,在被引用表中,必须在引用列上创建索引,这些列必须是索引的第一列,以相同的顺序。InnoDB 添加到索引中的隐藏列也被考虑在内(见 第 17.6.2.1 节,“聚簇索引和辅助索引”)。

    NDB 需要在任何作为外键引用的列上创建明确的唯一键(或主键)。InnoDB 不需要,这是标准 SQL 的扩展。

  • 外键列上的索引前缀不受支持。因此,BLOBTEXT 列不能包含在外键中,因为这些列上的索引必须始终包括前缀长度。

  • InnoDB 目前不支持用户定义的分区表的外键。这包括父表和子表。

    这条限制不适用于 NDB 表,这些表可以通过 KEYLINEAR KEY 进行分区(NDB 存储引擎支持的唯一用户定义的分区类型);这些表可以具有外键引用或是外键引用的目标。

  • 在外键关系中,不能将表更改为使用另一个存储引擎。要更改存储引擎,必须首先删除任何外键约束。

  • 外键约束不能引用虚拟生成的列。

有关 MySQL 实现外键约束与 SQL 标准的差异,请参阅 第 1.6.2.3 节,“FOREIGN KEY 约束差异”

参照操作

UPDATEDELETE操作影响父表中的键值,并且在子表中有匹配的行时,结果取决于参照操作ON UPDATEON DELETE子句指定的FOREIGN KEY子句。参照操作包括:

  • CASCADE:从父表中删除或更新行,并自动删除或更新子表中的匹配行。同时支持ON DELETE CASCADEON UPDATE CASCADE。在两个表之间,不要定义多个ON UPDATE CASCADE子句,作用于父表或子表中的同一列。

    如果在foreign key关系中的两个表上都定义了FOREIGN KEY子句,使得两个表都是父表和子表,那么在一个FOREIGN KEY子句中定义的ON UPDATE CASCADEON DELETE CASCADE子句必须在另一个FOREIGN KEY子句中定义,以便级联操作成功。如果只有一个FOREIGN KEY子句定义了ON UPDATE CASCADEON DELETE CASCADE子句,级联操作将失败并报错。

    Note

    级联foreign key操作不会激活触发器。

  • SET NULL:从父表中删除或更新行,并将子表中的foreign key列或列设置为NULL。同时支持ON DELETE SET NULLON UPDATE SET NULL子句。

    如果您指定了SET NULL操作,请确保您没有在子表中将列声明为NOT NULL

  • RESTRICT:拒绝父表的删除或更新操作。指定RESTRICT(或NO ACTION)与省略ON DELETEON UPDATE子句相同。

  • 不采取行动: 来自标准SQL的关键字。对于InnoDB,这等同于RESTRICT;父表的删除或更新操作将立即被拒绝,如果在引用的表中存在相关的外键值。NDB支持延迟检查,不采取行动指定了延迟检查;当使用时,约束检查将在提交时执行。注意,对于NDB表,所有外键检查都将被延迟执行。

  • 设置默认值: MySQL解析器识别这个动作,但是InnoDBNDB都拒绝包含ON DELETE SET DEFAULTON UPDATE SET DEFAULT子句的表定义。

对于支持外键的存储引擎,MySQL将拒绝任何INSERTUPDATE操作,该操作尝试在子表中创建外键值,而在父表中没有匹配的候选键值。

对于未指定的ON DELETEON UPDATE,默认操作始终是不采取行动

默认情况下,明确指定的ON DELETE NO ACTIONON UPDATE NO ACTION子句不会出现在SHOW CREATE TABLE输出中,也不会出现在使用mysqldump导出的表中。RESTRICT,这是一个等效的非默认关键字,将出现在SHOW CREATE TABLE输出中,也将出现在使用mysqldump导出的表中。

对于NDB表,ON UPDATE CASCADE不支持父表的主键引用。

对于 NDB 表,ON DELETE CASCADE 不支持在子表包含一个或多个 TEXTBLOB 类型的列时。(Bug #89511, Bug #27484882)

InnoDB 使用深度优先搜索算法在对应于外键约束的索引的记录上执行级联操作。

存储生成列上的外键约束不能使用 CASCADESET NULLSET DEFAULT 作为 ON UPDATE 引用操作,也不能使用 SET NULLSET DEFAULT 作为 ON DELETE 引用操作。

存储生成列的基列上的外键约束不能使用 CASCADESET NULLSET DEFAULT 作为 ON UPDATEON DELETE 引用操作。

外键约束示例

这个简单的示例将 parentchild 表通过单列外键关联:

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)。对于级联更新,将在相关表上获取共享-nothing 写锁 (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_FOREIGNINNODB_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;;
...
Warning

如果用户拥有所有父表的表级权限,ER_NO_REFERENCED_ROW_2ER_ROW_IS_REFERENCED_2 错误消息将暴露父表信息。如果用户没有所有父表的表级权限,将显示更通用的错误消息(ER_NO_REFERENCED_ROWER_ROW_IS_REFERENCED)。

一个例外是,对于使用 DEFINER 权限执行的存储程序,评估权限的用户是程序 DEFINER 子句中的用户,而不是调用用户。如果该用户拥有父表的表级权限,仍将显示父表信息。在这种情况下,存储程序的创建者有责任通过包含适当的条件处理程序来隐藏信息。