MySQL外鍵約束

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表爲從表(子表)。

可以得出以下結論:

  1. 默認情況下,如果子表中有匹配的記錄時,則不允許對父表中被引用的鍵進行更新/刪除操作。
  2. 子表外鍵值只能從父表中已有的指定,或爲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    |
+----+-----------+-----+-----+----------+------------+---------+

可以很明確的看到,同步刪除了。

結論:

  1. 如果在外鍵上設置了ON UPDATE CASCADE屬性後,在父表上更新記錄時,子表上匹配的記錄也會同步更新。
  2. 如果在外鍵上設置了ON DELETE CASCADE屬性後,在父表上刪除記錄時,子表上匹配的記錄也會同步刪除。

注:

  • 最好不要設置成ON DELETE CASCADE約束類型,因爲如果誤刪了父表中的數據,可能會導致數據丟失。推薦採用默認約束類型,如果需要設置刪除時的級聯關係最好設置爲SET NULL約束類型。
發佈了41 篇原創文章 · 獲贊 18 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章