1. 导言
上一节详细介绍了MySQL的查询操作,其中有涉及到对多张表的操作。我们不得不想以下问题:
- 添加employee时,有没有可能将dept_id添加为一个不存在的部门id?
- 如果错误的删除了部门表中的某一列,而恰恰有某个员工属于该部门,那会出现什么问题,该如何避免?
- 如果dept表中的did被更改了,那关联岂不是失效了?
2. 外键约束语法
解决上述问题的方式很简单,只需要对employee表添加外键约束即可,在新创建表时添加外键约束语句:
constraint fk_did foreign key(dept_id) references dept(did)
上述语句中:
- fk_did 为外键约束名称
- foreign key(dept_id)指定了本表中的dept_id字段为外键约定的关系字段
- references dept(did)指定了dept_id受dept表中的did字段约束
创建employee表时的语句:
CREATE TABLE `employee` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(60) NOT NULL COMMENT '姓名',
`age` tinyint(4) DEFAULT NULL COMMENT '年龄',
`sex` tinyint(2) NOT NULL DEFAULT '1' COMMENT '性别,1男,2女',
`salary` decimal(10,2) NOT NULL COMMENT '薪资',
`hire_date` date NOT NULL COMMENT '聘用日期',
`dept_id` int(11) DEFAULT NULL COMMENT '部门ID',
PRIMARY KEY (`id`),
KEY `fk_did` (`dept_id`),
CONSTRAINT `fk_did` FOREIGN KEY (`dept_id`) REFERENCES `dept` (`did`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;
当给员工指定一个不存在的部门id时:
mysql> insert into employee(name,age,sex,salary,hire_date,dept_id) values("老张","35","1","15000",NOW(),13);
1452 - Cannot add or update a child row: a foreign key constraint fails (`test`.`employee`, CONSTRAINT `fk_did` FOREIGN KEY (`dept_id`) REFERENCES `dept` (`did`))
当删除一个有员工的部门时:
mysql> delete from dept where dept.did = 1;
1451 - Cannot delete or update a parent row: a foreign key constraint fails (`test`.`employee`, CONSTRAINT `fk_did` FOREIGN KEY (`dept_id`) REFERENCES `dept` (`did`))
当视图更新被关联的外键的值时:
mysql> update dept set did = 7 where did = 1;
1451 - Cannot delete or update a parent row: a foreign key constraint fails (`test`.`employee`, CONSTRAINT `fk_did` FOREIGN KEY (`dept_id`) REFERENCES `dept` (`did`))
在本例中,employee引用了dept表中的did字段作为外键。所以dept表相对于employee表为主表(父表),employee表相对于dept表为从表(子表)。
可以得出以下结论:
- 默认情况下,如果子表中有匹配的记录时,则不允许对父表中被引用的键进行更新/删除操作。
- 子表外键值只能从父表中已有的指定,或为NULL。
3. 注意事项
- 子表外键的值要么引用自父表,要么为NULL。
- 父表中被引用的字段必须要保证唯一性,一般为该表的主键,如果引用的是普通字段,最好使用UNIQUE限制一下唯一性。
- table引擎需要设置为InnoDB。
- 插入数据时,先在父表中插入数据,再从子表中插入数据。
- 删除数据时,先删除子表中的数据,再删除父表中的数据。
4. 约束类型
在上面我们得出过一个结论:
默认情况下,如果子表中有匹配的记录时,则不允许对父表中被引用的键进行更新/删除操作。
注意,上面提到了“默认情况下”,也就是说默认的约束类型是不允许对父表中被引用的键进行更新/删除操作(可以对父表中的其他列进行操作),默认的约束类型是可以更改的。MySQL针对update/delete有四种约束类型,分别是:
- RESTRICT,默认约束类型,不允许对父表中被引用的键进行更新/删除操作。
- NO ACTION,同RESTRICT,在MySQL中都是立即检查外键约束。
- CASCADE,在父表上更新/删除记录时,同步删除子表的匹配记录。
- SET NULL,在父表上更新/删除记录时,将子表上匹配的记录的列设置为NULL。
4.1 SET NULL 约束类型
在外键约束语句中添加以下内容即设置了删除时SET NULL,更新时SET NULL约束类型
ON DELETE SET NULL ON UPDATE SET NULL
完整的外键约束语句如下
CONSTRAINT `fk_did` FOREIGN KEY (`dept_id`) REFERENCES `dept` (`did`) ON DELETE SET NULL ON UPDATE SET NULL
在操作之前先看一下表中有关联的数据:
mysql> select * from employee,dept where employee.dept_id = dept.did;
+----+-----------+-----+-----+----------+------------+---------+-----+-----------+
| id | name | age | sex | salary | hire_date | dept_id | did | dname |
+----+-----------+-----+-----+----------+------------+---------+-----+-----------+
| 1 | 菜虚鲲 | 20 | 2 | 10000.00 | 2020-01-10 | 1 | 1 | 研发部 |
| 2 | 奥力给 | 30 | 1 | 18000.00 | 2020-01-08 | 1 | 1 | 研发部 |
| 3 | 老八 | 28 | 1 | 7000.00 | 2020-01-07 | 1 | 1 | 研发部 |
| 4 | 小张 | 25 | 1 | 8000.00 | 2020-01-10 | 1 | 1 | 研发部 |
| 5 | 小红 | 20 | 2 | 6000.00 | 2020-01-05 | 2 | 2 | 人事部 |
| 6 | 小丽 | 23 | 2 | 6500.00 | 2020-01-05 | 2 | 2 | 人事部 |
| 7 | 小花 | 21 | 2 | 5500.00 | 2020-01-10 | 2 | 2 | 人事部 |
| 8 | 马小跳 | 25 | 1 | 7000.00 | 2020-01-01 | 3 | 3 | 测试部 |
| 9 | 张大骚 | 30 | 1 | 9000.00 | 2020-01-07 | 3 | 3 | 测试部 |
| 10 | 马冬梅 | 31 | 2 | 5000.00 | 2020-01-07 | 4 | 4 | 销售部 |
+----+-----------+-----+-----+----------+------------+---------+-----+-----------+
10 rows in set (0.00 sec)
可以明确的看到马小跳和张大骚两列引用的外键id为3,更新dept表,将did = 3 的行更改为did = 7
mysql> update dept set did = 7 where did = 3;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
看一下表中的数据:
mysql> select * from employee left join dept on employee.dept_id = dept.did;
+----+-----------+-----+-----+----------+------------+---------+------+-----------+
| id | name | age | sex | salary | hire_date | dept_id | did | dname |
+----+-----------+-----+-----+----------+------------+---------+------+-----------+
| 1 | 菜虚鲲 | 20 | 2 | 10000.00 | 2020-01-10 | 1 | 1 | 研发部 |
| 2 | 奥力给 | 30 | 1 | 18000.00 | 2020-01-08 | 1 | 1 | 研发部 |
| 3 | 老八 | 28 | 1 | 7000.00 | 2020-01-07 | 1 | 1 | 研发部 |
| 4 | 小张 | 25 | 1 | 8000.00 | 2020-01-10 | 1 | 1 | 研发部 |
| 5 | 小红 | 20 | 2 | 6000.00 | 2020-01-05 | 2 | 2 | 人事部 |
| 6 | 小丽 | 23 | 2 | 6500.00 | 2020-01-05 | 2 | 2 | 人事部 |
| 7 | 小花 | 21 | 2 | 5500.00 | 2020-01-10 | 2 | 2 | 人事部 |
| 10 | 马冬梅 | 31 | 2 | 5000.00 | 2020-01-07 | 4 | 4 | 销售部 |
| 8 | 马小跳 | 25 | 1 | 7000.00 | 2020-01-01 | NULL | NULL | NULL |
| 9 | 张大骚 | 30 | 1 | 9000.00 | 2020-01-07 | NULL | NULL | NULL |
| 11 | 川坚果 | 60 | 1 | 100.00 | 2020-01-08 | NULL | NULL | NULL |
+----+-----------+-----+-----+----------+------------+---------+------+-----------+
11 rows in set (0.00 sec)
可以看到马小跳和张大骚里的dept_id已经变成了NULL。
由此得出结论:
如果在外键上设置了ON UPDATE SET NULL属性后,在父表上更新记录时,将子表上匹配的记录的列设置为NULL。
ON DELETE SET NULL效果也一样,区别就是在删除时设置为NULL,这里就不展示了。
4.2 CASCADE约束类型
使用CASCADE时完整的外键约束语句如下:
CONSTRAINT `fk_did` FOREIGN KEY (`dept_id`) REFERENCES `dept` (`did`) ON DELETE CASCADE ON UPDATE CASCADE
在操作之前先看一下当前的关联数据:
mysql> select * from employee,dept where employee.dept_id = dept.did;
+----+-----------+-----+-----+----------+------------+---------+-----+-----------+
| id | name | age | sex | salary | hire_date | dept_id | did | dname |
+----+-----------+-----+-----+----------+------------+---------+-----+-----------+
| 1 | 菜虚鲲 | 20 | 2 | 10000.00 | 2020-01-10 | 1 | 1 | 研发部 |
| 2 | 奥力给 | 30 | 1 | 18000.00 | 2020-01-08 | 1 | 1 | 研发部 |
| 3 | 老八 | 28 | 1 | 7000.00 | 2020-01-07 | 1 | 1 | 研发部 |
| 4 | 小张 | 25 | 1 | 8000.00 | 2020-01-10 | 1 | 1 | 研发部 |
| 5 | 小红 | 20 | 2 | 6000.00 | 2020-01-05 | 2 | 2 | 人事部 |
| 6 | 小丽 | 23 | 2 | 6500.00 | 2020-01-05 | 2 | 2 | 人事部 |
| 7 | 小花 | 21 | 2 | 5500.00 | 2020-01-10 | 2 | 2 | 人事部 |
| 10 | 马冬梅 | 31 | 2 | 5000.00 | 2020-01-07 | 4 | 4 | 销售部 |
+----+-----------+-----+-----+----------+------------+---------+-----+-----------+
8 rows in set (0.00 sec)
这次以小红、小丽、小花所在did=2的部门为例,将dept表中did = 2的列的did修改为8:
mysql> update dept set did = 8 where did = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
再看一下表中的关联数据:
mysql> select * from employee,dept where employee.dept_id = dept.did;
+----+-----------+-----+-----+----------+------------+---------+-----+-----------+
| id | name | age | sex | salary | hire_date | dept_id | did | dname |
+----+-----------+-----+-----+----------+------------+---------+-----+-----------+
| 1 | 菜虚鲲 | 20 | 2 | 10000.00 | 2020-01-10 | 1 | 1 | 研发部 |
| 2 | 奥力给 | 30 | 1 | 18000.00 | 2020-01-08 | 1 | 1 | 研发部 |
| 3 | 老八 | 28 | 1 | 7000.00 | 2020-01-07 | 1 | 1 | 研发部 |
| 4 | 小张 | 25 | 1 | 8000.00 | 2020-01-10 | 1 | 1 | 研发部 |
| 5 | 小红 | 20 | 2 | 6000.00 | 2020-01-05 | 8 | 8 | 人事部 |
| 6 | 小丽 | 23 | 2 | 6500.00 | 2020-01-05 | 8 | 8 | 人事部 |
| 7 | 小花 | 21 | 2 | 5500.00 | 2020-01-10 | 8 | 8 | 人事部 |
| 10 | 马冬梅 | 31 | 2 | 5000.00 | 2020-01-07 | 4 | 4 | 销售部 |
+----+-----------+-----+-----+----------+------------+---------+-----+-----------+
8 rows in set (0.00 sec)
可以看到同步修改成了8。
那,现在删除一下dept表中did = 8 的列试一试:
mysql> delete from dept where did = 8;
Query OK, 1 row affected (0.02 sec)
再看看employee中中是否还有小红、小丽、小花这三列:
mysql> select * from employee;
+----+-----------+-----+-----+----------+------------+---------+
| id | name | age | sex | salary | hire_date | dept_id |
+----+-----------+-----+-----+----------+------------+---------+
| 1 | 菜虚鲲 | 20 | 2 | 10000.00 | 2020-01-10 | 1 |
| 2 | 奥力给 | 30 | 1 | 18000.00 | 2020-01-08 | 1 |
| 3 | 老八 | 28 | 1 | 7000.00 | 2020-01-07 | 1 |
| 4 | 小张 | 25 | 1 | 8000.00 | 2020-01-10 | 1 |
| 8 | 马小跳 | 25 | 1 | 7000.00 | 2020-01-01 | NULL |
| 9 | 张大骚 | 30 | 1 | 9000.00 | 2020-01-07 | NULL |
| 10 | 马冬梅 | 31 | 2 | 5000.00 | 2020-01-07 | 4 |
| 11 | 川坚果 | 60 | 1 | 100.00 | 2020-01-08 | NULL |
+----+-----------+-----+-----+----------+------------+---------+
可以很明确的看到,同步删除了。
结论:
- 如果在外键上设置了ON UPDATE CASCADE属性后,在父表上更新记录时,子表上匹配的记录也会同步更新。
- 如果在外键上设置了ON DELETE CASCADE属性后,在父表上删除记录时,子表上匹配的记录也会同步删除。
注:
- 最好不要设置成ON DELETE CASCADE约束类型,因为如果误删了父表中的数据,可能会导致数据丢失。推荐采用默认约束类型,如果需要设置删除时的级联关系最好设置为SET NULL约束类型。