什么是外键约束
外键是表中的一列或一组列链接到另外一张表的一列或一组列。外键会在相关联的表中起到约束作用,保证数据的一致性和实现一些级联操作。
如果在表A中建立外键,关联到表B,那么表B为主表,表A为从表。主表B中的对应的列的更新或删除会联动到外键所在的表A中的相应的列的操作(具体的操作根据在表A中添加外键时的配置不同而不同)
即,建立外键的表为从表,被外键关联的表是主表。
创建外键约束时,会针对本表中相应的行自动创建索引。
外键的使用条件
- 外键只适用于InnoDB引擎,MyISAM不支持。
- 外键列必须建立了索引,MySQL 4.1.2以后的版本在建立外键时会自动创建索引,但如果在较早的版本则需要显式建立;
- 外键关系的两个表的列必须是数据类型相似,也就是可以相互转换类型的列,比如int和tinyint可以,而int和char则不可以;
简单介绍
假设存在两张表customers和orders。每一个custormer可以有0个或多个orders,同样的,每个order都属于某一个customer。
可以看出custormers和orders表是一对多的关系。并且这个关系通过orders表中的外键的customer_id列来建立。
在oders表中的custormer_id列链接到customers表中的id主键列。
此时,customers表通常被称为主表(父表),orders表被称为子表。
通常情况下,外键都是关联到主表的主键列上面。
子表上面可以创建多个外键并关联到多个主表的主键列上。
一旦外键约束就位。外键约束的列的值需要在主表的主键列上存在,或者为NULL(此时外键约束的action是SET NULL)
例如,orders表中的customer_id列的值需要存在与customers表的id列上。oders表中的多个行可以拥有相同的custormer_id
自引用的外键
有些时候,子表和主表可能是同一张表。这种情况下外键引用的是当前表的主键
比如下面这张表employees:

其中reportTo字段是一个外键字段,它指向本表的主键列employeeNumber。
这种关系允许employees表存储雇员与管理人员的关系结构。每个雇员都有0个或1个上级,且每个雇员可以拥有0个或多个下级。
此时,reportTo列上的外键就叫做递归或自引用外键。
创建外键的SQL语法
通过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]
首先在CONSTRAINT关键字后面指定外键约束的名字。如果省略名字,那么MySQL会为此外键约束自动创建一个名字。
接下来,通过FOREIGN KEY关键字来指定此外键的列,多个列的话用逗号分隔开来,同样的外键的名字也是可省略的。
第三步指定主表和主表上被引用到的列,多列用逗号分隔。
最后,指定外键在子表和主表之间联动的动作(action),这些动作分为ON DELETE和ON UPDATE。reference_option表示子表中此外键关联的列所采取的行动,当主表中被引用的列被删除(ON DELETE)或更新(ON UPDATE)时。
MySQL有5种reference options:CASCADE, SET NULL, NO ACTION, RESTRICT, and SET DEFAULT:
-
CASCADE:级联操作,如果父表中的一行被删除或更新,子表会自动跟着删除或更新。 -
SET NULL:如果父表中的行被删除或更新,子表中相应的列的值被设置为NULL。 -
RESTRICT:如果父表中被外键引用的列的值在子表中存在相应的行与之匹配,MySQL拒绝父表的这个删除或更新操作。 -
NO ACTION:同RESTRICT -
SET DEFAULT:MySQL的语法分析器可以识别,但是InnoDB和NDB引擎不支持。
实际上,MySQL支持三种actions:RESTRICT, CASCADE and SET NULL。
如果没有设置ON DELETE和ON UPDATE,那么默认的action是RESTRICT。
开始实验
下面通过实际的操作来体验外键的功能。
创建两张表custormers和orders
CREATE TABLE customers(
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL
) ENGINE=INNODB;
CREATE TABLE orders(
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT,
order_info VARCHAR(255)
) ENGINE=INNODB;
RESTRICT & NO ACTION actions
在orders表中创建外键,使用默认的RESTRICTaction。
ALTER TABLE orders
ADD FOREIGN KEY (customer_id)
REFERENCES customers(id);
未指定action时,默认
ON DELETE和ON UPDATE都是RESTRICT
在customers表中插入数据:
INSERT INTO customers (name) values ('andy'),('jerry');
此时customser表中的数据为:
| id | name |
|---|---|
| 1 | andy |
| 2 | jerry |
在orders表中插入新的行:
INSERT INTO orders (customer_id,order_info) values (1,'info');
插入成功,因为id为1的customer_id存在于customers表中。
那么接下来在orders表中插入一条customer_id不存在的数据看看会发生什么:
INSERT INTO orders (customer_id,order_info) values (3,'info');
出现了如下的报错信息:
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`test`.`orders`, CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`customer_id`) REFERENCES `customers` (`id`))
那么接下来更新一下customers表中id为1的行试试看:
UPDATE customers set id = 5 where id = 1;
出现了报错:
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`test`.`orders`, CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`customer_id`) REFERENCES `customers` (`id`))
因为RESTRICTaction,如果子表中存在父表中外键约束引用到的列的值,那么mysql会阻止了父表的更新和删除操作。
下面的更新就不会出错,因为id为2的customer_id不存在于oders表中。
UPDATE customers set id = 5 where id = 2;
CASCADE action
删除外键约束
需要使用两条命令来删除:
//删除外键约束
ALTER TABLE orders DROP FOREIGN KEY `orders_ibfk_1`;
//删除创建外键约束时自动创建的索引
ALTER TABLE orders DROP INDEX `customer_id`;
注意删除外键约束的时候,使用语句
ALTER TABLE example_table DROP FOREIGN KEY `constraint_name`;这里的constraint_name是外键约束的名字,而不是外键的名字,如果创建约束的时候没有指定名字,那么可以通过SHOW CREATE TABLE example_table命令查看。
同时,上面的命令删除了外键约束并不会同步删除创建外键约束是的对应列的索引,所以需要额外的一条命令去删除它。
创建新的外键约束,使用CASCADEaction:
ALTER TABLE orders
ADD FOREIGN KEY (customer_id)
REFERENCES customers(id)
ON UPDATE CASCADE
ON DELETE CASCADE;
此时orders表中的数据如下:
| id | customer_id | order_info |
|---|---|---|
| 1 | 1 | info |
将custorms表中的id为1的行改为10:
UPDATE customers set id = 10 where id = 1;
更新成功之后,查看orders表中的数据:
| id | customer_id | order_info |
|---|---|---|
| 1 | 10 | info |
发现其customer_id列的值也同步更新为了10
接下来删除customsers表中id为10的行:
DELETE FROM customers where id = 10;
执行成功之后查看orders表中的数据为空,其与主表关联的行数据也被删除了。
SET NULL action
删除外键约束
需要使用两条命令来删除:
//删除外键约束
ALTER TABLE orders DROP FOREIGN KEY `orders_ibfk_1`;
//删除创建外键约束时自动创建的索引
ALTER TABLE orders DROP INDEX `customer_id`;
创建新的外键约束,使用CASCADEaction:
ALTER TABLE orders
ADD FOREIGN KEY (customer_id)
REFERENCES customers(id)
ON UPDATE SET NULL
ON DELETE SET NULL;
这里注意创建外键约束的列
customer_id不能使用NOT NULL语句,不然无法创建SET NULLaction的外键约束
往orders插入新的数据(上个步骤的删除行操作已经将orders表的数据删除了)
INSERT INTO orders (customer_id,order_info) values (5,'info');
| id | customer_id | order_info |
|---|---|---|
| 1 | 5 | info |
更新customers表中id为5个行,将id改为50:
UPDATE customers set id = 50 where id = 5;
查看orders表:
| id | customer_id | order_info |
|---|---|---|
| 1 | NULL | info |
原先customer_id为5的那一行数据,现在值变为了NULL,这是因为ON UPDATE SET NULL起作用了
往customsers和orders表中都插入新的测试数据
INSERT INTO customers (id,name) values (8,'andy');
INSERT INTO orders (customer_id,order_info) values (8,'info');
现在customsers表和orders表的数据分别如下:
| id | name |
|---|---|
| 8 | andy |
| 50 | jerry |
| id | customer_id | order_info |
|---|---|---|
| 1 | NULL | info |
| 4 | 8 | info |
删除customers表中新增的id为8的数据:
DELETE FROM customers where id = 8;
此时orders表中的相对应的行的customer_id列的值变为了NULL:
| id | customer_id | order_info |
|---|---|---|
| 1 | NULL | info |
| 4 | NULL | info |
这是因为外键约束ON DELETE SET NULL action 起了作用
禁用外键检查
有些时候当我们需要从一个表中导入数据是,如果存在外键,那么导入和修改的顺序就不能错乱,必须严格遵循先导入主表然后再导入子表的顺序。此时可以通过改变变量的形式来临时禁用外键检查。
禁用外键检查:
SET foreign_key_checks = 0;
启用外键检查:
SET foreign_key_checks = 1;
参考
An Essential Guide to MySQL Foreign Key By Practical Examples
Using FOREIGN KEY Constraints
mysql 外键(foreign key)的详解和实例