数据库范式是一个比较抽象的概念,对于关系型数据库来说,有效的解决关系数据库中避免数据冗余,减少数据库的存储空间,并且减轻维护数据完整性的成本。
数据库范式也是关系型数据库核心的设计模式之一,也是从事数据库开发人员必备知识。其中外键适用于3NF第三范式,在数据库中的作用是保持数据一致性,完整性。主要目的是使两张表形成关联,控制存储在外键表中的数据。 外键在一定程度上说明的业务逻辑,会使设计周到具体全面。
特别是,MySQL已主键驱动数据,经验告诉我们,外键其实不适合。实际环境中很少使用外键,一般都是程序实现关联关系。
外键理解
在MySQL外键的关联中,包含多种action关联,如语法中体现:
[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
注意:
-
目前外键使用范围 InnoDB和NDB存储引擎都会使用FOREIGN_KEY,还有受到lower_case_table_names大小写敏感系统变量。
-
父子表必须使用相同的存储引擎,并且不能将它们定义为临时表。
创建外键约束需要父表上的REFERENCES权限。 -
外键和引用键中的对应列必须具有类似的数据类型。固定精度类型(如INTEGER、DECIMAL)的大小和符号必须相同。字符串类型的长度不必相同。对于非二进制(字符)字符串列,字符集和排序规则必须相同。
-
在外键引用表中,必须有一个索引,其中外键列以相同顺序列在第一列。以便外键检查可以快速,不需要全表扫描。
-
NDB要求在作为外键引用的任何列上都有一个显式的唯一键(或主键)。InnoDB不是。
-
不支持外键列上的索引前缀,BLOB和TEXT列不能包含在外键中。
-
InnoDB目前不支持分区的表的外键
-
外键约束不能引用虚拟生成的列。
外键的行为
从语法上包含三个Action 直接引用,update ,delete 对应操作:
-
CASCADE:删除或更新父表中的行,并自动删除或更新子表中匹配的行。
-
SET NULL:从父表中删除或更新行,并将子表中的外键列或列设置为NULL.
-
RESTRICT:拒绝父表的删除或更新操作。
-
NO ACTION:来自标准SQL的关键字。在MySQL中,相当于RESTRICT。如果在被引用的表中有一个相关的外键值,MySQL服务器拒绝对父表进行删除或更新操作。一些数据库系统有延期检查,NO ACTION就是延期检查。在MySQL中,外键约束被立即检查,所以NO ACTION与RESTRICT相同
-
SET DEFAULT:这个动作被MySQL解析器识别,但是InnoDB和NDB都拒绝包含ON DELETE SET DEFAULT或ON UPDATE SET DEFAULT子句的表定义。
备注:对于未指定的ON DELETE或ON UPDATE,默认操作总是NO action
InnoDB使用深度优先搜索算法对对应外键约束的索引记录执行级联操作。
锁机制
MySQL根据需要将元数据锁扩展到外键约束相关的表。对于外键检查,在相关表上使用共享只读锁(lock TABLES READ)。对于级联更新,在操作涉及的相关表上使用一个无共享的写锁(lock TABLES write)。当存在外键约束的时候,每次要去扫描此记录是否合格。所以一段关联字段操作,就容易会出现死锁现象。
#结构
CREATE TABLE categories(
cat_id int not null auto_increment primary key,
cat_name varchar(255) not null
) ENGINE=InnoDB;
CREATE TABLE products(
prd_id int not null auto_increment primary key,
prd_name varchar(355) not null,
prd_price decimal,
cat_id int not null,
FOREIGN KEY fk_cat(cat_id) REFERENCES categories(cat_id)
ON UPDATE CASCADE
ON DELETE RESTRICT
)ENGINE=InnoDB;
#数据
insert into categories(cat_id,cat_name) values(1,'水果'),(2,'蔬菜');
insert into products(prd_id,prd_name,prd_price,cat_id) values(1,'苹果',9,1),(2,'菠萝',10,1),(3,'白菜',8,2),(4,'可乐',7,3);
1)有意思的演示:
父表插入数据未提交,子表更新等待。
父表进行更新未提交,子表插入等待。
| session1 | session2 |
|---|---|
![]() |
![]() |
![]() |
![]() |
#外键最少2张表操作
mysql> show OPEN TABLES where In_use > 0;
+----------+----------+--------+-------------+
| Database | Table | In_use | Name_locked |
+----------+----------+--------+-------------+
| dbdemo | products | 2 | 0 |
+----------+----------+--------+-------------+
mysql> SHOW ENGINE INNODB STATUS\G
#外键错误
------------------------
LATEST FOREIGN KEY ERROR
------------------------
2022-05-31 17:22:52 140626893833984 Transaction:
TRANSACTION 9011856, ACTIVE 0 sec updating or deleting
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1128, 2 row lock(s), undo log entries 1
MySQL thread id 23, OS thread handle 140626893833984, query id 168 localhost root updating
update categories set cat_id=3 where cat_id=2
Foreign key constraint fails for table `dbdemo`.`products`:
,
CONSTRAINT `products_ibfk_1` FOREIGN KEY (`cat_id`) REFERENCES `categories` (`cat_id`)
Trying to update in parent table, in index PRIMARY tuple:
DATA TUPLE: 4 fields;
0: len 4; hex 80000002; asc ;;
1: len 6; hex 000000898290; asc ;;
2: len 7; hex 01000001970151; asc Q;;
3: len 6; hex e894ace88f9c; asc ;;
But in child table `dbdemo`.`products`, in index fk_cat, there is a record:
PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 80000002; asc ;;
1: len 4; hex 80000003; asc ;;
2)DDL操作受外键约束:
mysql > TRUNCATE TABLE products;
Query OK, 0 rows affected (0.03 sec)
mysql > TRUNCATE TABLE categories;
ERROR 1701 (42000): Cannot truncate a table referenced in a foreign key constraint (`dbdemo`.`products`, CONSTRAINT `products_ibfk_1`)
mysql> DROP TABLE categories;
ERROR 3730 (HY000): Cannot drop table 'categories' referenced by a foreign key constraint 'products_ibfk_1' on table 'products'.
注意:MySQL里外键是即时检查的,对每一行都会运行外键检查。如上述情况 或 用load data 导入数据,在检查外键约束上往往消耗大量时间。有时候,可以灵活处理,忽略外键检查:SET foreign_key_checks=0,默认值是1。
3)子表的里外键值是实际保存的数据,一旦父表外键更新,那子表随着更新。可以理解两张表索引结构变更。其实一个动作触发双倍的索引操作,IO放大。
总结
外键在MySQL提现有两种,一个是如何保证数据库数据的完整性和一致性;二是性能的影响。
不用外键时数据管理简单,操作方便,性能高(导入导出等操作,在insert, update, delete 数据的时候更快)
1.有了外键,当做一些涉及外键字段的增,删,更新操作之后,需要触发相关操作去检查,而不得不消耗资源;
2.当删除,更新操作底层需要重新重组索引表,这样导致上锁的时间延长;
3.外键还会因为需要请求对其他表内部加锁而容易出现死锁情况;
4.数据库需要维护外键的内部管理;
综合这些因素,不推荐在关系型数据库使用外键,除了把控不好,在MySQL这样轻量级数据库上,影响范围比较大,建议全靠代码逻辑去保证。








