mysql 高版本检索外键_第05期:外键到底能不能用?

c3933b2dc5a70271804720ba03dad769.png

外键的设计初衷是为了在数据库端保证对逻辑上相关联的表数据在操作上的一致性与完整性。

外键在大部分企业写的开发规范里会直接规避掉!外键有优缺点,也并不是说每种场景都不适用,完全没有必要一刀切。外键到底能不能用?下面会针对不同的场景来告诉你答案。

一、外键的优缺点

优点:

  • 精简关联数据,减少数据冗余避免后期对大量冗余处理的额外运维操作。
  • 降低应用代码复杂性,减少了额外的异常处理相关数据管理全由数据库端处理。
  • 增加文档的可读性特别是在表设计开始,绘制 ER 图的时候,逻辑简单明了,可读性非常强。

缺点:

  • 性能压力外键一般会存在级联功能,级联更新,级联删除等等。在海量数据场景,造成很大的性能压力。比如插入一条新记录,如果插入记录的表有 10 个外键,那势必要对关联的 10 张表逐一检查插入的记录是否合理,延误了正常插入的记录时间。并且父表的更新会连带子表加上相关的锁。
  • 其他功能的灵活性不佳比如,表结构的更新等。

二、外键的使用

外键参照动作列表:

  • CASCADE:级联,子表跟随父表更新外键值
  • SET NULL:子表更随主表更新外键值为 NULL
  • RESTRICT/ NO ACTION:默认,限制父表改动外键值
  • SET DEFAULT:目前产生的效果和 RESTRICT 相同。

那先来简单看看 MySQL 里外键的用法。MySQL 外键仅有 InnoDB 和 NDB 两种引擎支持,这里只关注 InnoDB。

本次示例 MySQL 的版本为最新版 8.0.19

示例

下面 f1 是父表,f2、f3、f6 分别代表不同类型的外键表,也就是子表。

-- 引用基础表,也就是父表mysql-(ytt_fk/3305)->create table f1(id int primary key,    r1 int, r2 int, r3 int,key idx_r1(r1),key idx_u1 (r2,r3));Query OK, 0 rows affected (0.02 sec)--   随着参照表级联更新外键表,也就是父表更新的话,会级联更新子表的外键mysql-(ytt_fk/3305)->create table f2(id int primary key,    f1_r1 int, mark int, constraint f1_fk_r1 foreign key (f1_r1) references f1(r1) on update cascade);Query OK, 0 rows affected (0.02 sec)--  随着参照表更新外键值为 NULL,也就是父表更新的话,会级联更新子表的外键为 NULLmysql-(ytt_fk/3305)->create table f3 (id int primary key,    f1_id int, foreign key (f1_id) references f1(id) on update set null);Query OK, 0 rows affected (0.02 sec)--  多个键值外键。子表的可以引用父表非主键的其他键mysql-(ytt_fk/3305)->create table f6 ( id int auto_increment primary key,    f1_r2 int, f1_r3 int, foreign key (f1_r2,f1_r3) references f1(r2,r3));Query OK, 0 rows affected (0.02 sec)

场景一:强烈要求数据一致性,程序弱化,数据库端强化,表结构改动小,并发不高的场景。

用一条记录验证表 f2 和 f6。从功能性角度来看,外键的优势很明显,在数据库端完全满足了数据完整性校验。

mysql-(ytt_fk/3305)->insert into f1 values (1,10,100,1000);Query OK, 1 row affected (0.00 sec)mysql-(ytt_fk/3305)->insert into f2 values (1,1);Query OK, 1 row affected (0.01 sec)mysql-(ytt_fk/3305)->insert into f6 values (1,100,1000);Query OK, 1 row affected (0.00 sec)-- 更新引用表 f1mysql-(ytt_fk/3305)->update f1 set id = 2 where id =1;Query OK, 1 row affected (0.01 sec)Rows matched: 1  Changed: 1  Warnings: 0-- f2 也成功级联更新mysql-(ytt_fk/3305)->select * from f2;+----+-------+| id | f1_id |+----+-------+|  1 |     2 |+----+-------+1 row in set (0.00 sec)-- 引用表 r2 字段不允许更新,因为表 f6 有针对字段 r2 的外键约束。mysql-(ytt_fk/3305)->update f1 set r2 = 11 ;ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`ytt_fk`.`f6`, CONSTRAINT `f6_ibfk_1` FOREIGN KEY (`f1_r2`, `f1_r3`) REFERENCES `f1` (`r2`, `r3`))

场景二:频繁的数据装载,但是也严格要求数据库端保证数据一致性。

这里只验证表 f6,同时克隆一张新表 f6_no_fk,除了没有外键,表结构和 f6 一样。导入 400W 条样例数据。

-- 导入 f6,有外键,时间 32 秒多。mysql-(ytt_fk/3305)->load data infile '/var/lib/mysql-files/f1_sub.dat' into table f6;Query OK, 4000000 rows affected (32.57 sec)Records: 4000000  Deleted: 0  Skipped: 0  Warnings: 0-- 导入 f6_no_fk,没有外键,时间 25 秒多。mysql-(ytt_fk/3305)->load data infile '/var/lib/mysql-files/f1_sub.dat' into table f6_no_fk;Query OK, 4000000 rows affected (25.95 sec)Records: 4000000  Deleted: 0  Skipped: 0  Warnings: 0

从上面看到,单独的测试导入 400W 条记录,带有外键的表比非外键的表时间上没有优势。那针对上面的场景优化下,关闭外键检查参数,导入完成后,再开启。

mysql-(ytt_fk/3305)->truncate f6;Query OK, 0 rows affected (0.04 sec)-- 关闭外键检查。mysql-(ytt_fk/3305)->set foreign_key_checks=0;Query OK, 0 rows affected (0.00 sec)-- 重新导入,时间28秒多。mysql-(ytt_fk/3305)->load data infile '/var/lib/mysql-files/f1_sub.dat' into table f6;Query OK, 4000000 rows affected (28.42 sec)Records: 4000000  Deleted: 0  Skipped: 0  Warnings: 0-- 开启外键检查。mysql-(ytt_fk/3305)->set foreign_key_checks=1;Query OK, 0 rows affected (0.00 sec)

从以上结果看出,关闭外键检查后,导入时间和没有外键的表 f6_no_fk 差不多。

场景三:并发少,事物块简单。

接下来再看下简单的事物块提交方式,我简单写了一个每 500 条记录提交一次的存储过程。

DELIMITER $$CREATE DEFINER=`ytt`@`127.0.0.1` PROCEDURE `sp_generate_data`(IN `tb_name` VARCHAR(64), IN `f_number` INT)begindeclare i int default 0;set @@autocommit=0;while i < f_number DO  set @stmt = concat("insert into ",tb_name,"(f1_r2,f1_r3) values (ceil(rand()*10),ceil(rand()*10))");  prepare s1 from @stmt;  execute s1;  set i = i + 1;  if mod(i,500)=0 THEN    commit;  end if;end while;drop prepare s1;commit;set @@autocommit=1;end$$DELIMITER ;

接下来插入 100W 条记录,

-- 外键表写入总时间为 1 分 14 秒mysql> call sp_generate_data('f6',1000000);Query OK, 0 rows affected (1 min 14.14 sec)-- 非外键表写入时间为 1 分 8 秒mysql> call sp_generate_data('f6_no_fk',1000000);Query OK, 0 rows affected (1 min 8.45 sec)-- 关闭外键检查mysql> set foreign_key_checks=0;Query OK, 0 rows affected (0.00 sec)-- 时间为 1 分 4 秒mysql> call sp_generate_data('f6',1000000);Query OK, 0 rows affected (1 min 4.28 sec)mysql> set foreign_key_checks=1;Query OK, 0 rows affected (0.00 sec)

从测试的结果来看,有外键和没有外键的检索时间在这样的场景下也相差无几。

场景四:主表的外键引用字段类型要扩充,原来的数据溢出,没法保存更大的值。

比如此时字段 r2 定义的数据类型不合适了,需要更改为大点的,比如以下,直接修改会报错,

mysql-(ytt_fk/3305)->alter table f1 change r2 r2 bigint;ERROR 3780 (HY000): Referencing column 'f1_r2' and referenced column 'r2' in foreign key constraint 'f6_ibfk_1' are incompatible.mysql-(ytt_fk/3305)->alter table f6 change f1_r2 f1_r2 bigint;ERROR 3780 (HY000): Referencing column 'f1_r2' and referenced column 'r2' in foreign key constraint 'f6_ibfk_1' are incompatible.

那怎么改呢?需要先把外键删掉,修改完了类型,再加上约束。这种场景就不太适合用外键。

mysql-(ytt_fk/3305)->alter table f6 drop constraint f6_ibfk_1;Query OK, 0 rows affected (0.00 sec)Records: 0  Duplicates: 0  Warnings: 0mysql-(ytt_fk/3305)->alter table f6 change f1_r2 f1_r2 bigint;Query OK, 0 rows affected (0.04 sec)Records: 0  Duplicates: 0  Warnings: 0mysql-(ytt_fk/3305)->alter table f1 change r2 r2 bigint;Query OK, 100000 rows affected (0.73 sec)Records: 100000  Duplicates: 0  Warnings: 0mysql-(ytt_fk/3305)->alter table f6 add foreign key (f1_r2,f1_r3) references f1(r2,r3);Query OK, 0 rows affected (0.03 sec)Records: 0  Duplicates: 0  Warnings: 0

场景五:子表有触发器需求来更新必要的字段。

那关于这点就是,子表的触发器不会随着父表的更新级联应用,也就是此时触发器失效。举个例子,往 f2 上添加一个 before update 触发器。

-- 前置更新触发器CREATE TRIGGER `tr_af_update` BEFORE UPDATE ON `f2` FOR EACH ROW set new.mark = new.f1_r1;mysql-(ytt_fk/3305)->insert into f2 values (1,10,5);Query OK, 1 row affected (0.00 sec)mysql-(ytt_fk/3305)->select * from f2;+----+-------+------+| id | f1_r1 | mark |+----+-------+------+|  1 |    10 |    5 |+----+-------+------+1 row in set (0.00 sec)-- 更新父表,mysql-(ytt_fk/3305)->update f1 set r1 = 2 where r1 = 10;Query OK, 5133 rows affected (0.15 sec)Rows matched: 5133  Changed: 5133  Warnings: 0-- 子表 f2对应的级联做了更改,但是触发器动作没执行。mysql-(ytt_fk/3305)->select * from f2;+----+-------+------+| id | f1_r1 | mark |+----+-------+------+|  1 |     2 |    5 |+----+-------+------+1 row in set (0.00 sec)-- 正常的操作应该这样mysql-(ytt_fk/3305)->update f2 set id = 2;Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0-- mark字段对应的克隆成了f1_r1字段的值。mysql-(ytt_fk/3305)->select * from f2;+----+-------+------+| id | f1_r1 | mark |+----+-------+------+|  2 |     2 |    2 |+----+-------+------+1 row in set (0.00 sec)

场景六:父表为分区表,有外键的需求。

那针对分区表,暂时不支持子表以分区表为父表的外键。

mysql-(ytt_fk/3305)->create table f1_partition like f1;Query OK, 0 rows affected (0.02 sec)mysql-(ytt_fk/3305)->alter table f1_partition  partition by key() partitions 4;Query OK, 0 rows affected (0.10 sec)Records: 0  Duplicates: 0  Warnings: 0mysql-(ytt_fk/3305)->create table f7 ( id int primary key,    f1_partition_id int, foreign key (f1_partition_id) references f1_partition(id));ERROR 1506 (HY000): Foreign keys are not yet supported in conjunction with partitioning

场景七:日常并发很高的场景,应该尽量减少相关事务锁的范围和量级。

那举个简单例子,看看有外键情况下,父表更新,子表级联加锁的情形。

-- SESSION 1mysql-(ytt_fk/3305)->begin;Query OK, 0 rows affected (0.00 sec)mysql-(ytt_fk/3305)->update f1 set r2 = 101 where r2 = 100;Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0mysql-(ytt_fk/3305)->select sys.ps_thread_id(connection_id()) as cid;+------+| cid  |+------+|   47 |+------+1 row in set (0.00 sec)

总共有 11 个锁,也就简单的执行了下 Update,而且更新的只是一行。

-- SESSION 2mysql-((none)/3305)->select count(*) from performance_schema.data_locks where thread_id = 47;+----------+| count(*) |+----------+|       11 |+----------+1 row in set (0.00 sec)

查看锁的细化,父有 f1 有 5 个锁,子表 f6 有 6 个锁。

这都是 MySQL 为了保证数据一致性强制加的,这点在 TPS 要求比较高的场景肯定不合适,

mysql-((none)/3305)->select object_name,lock_type,lock_mode,lock_status,lock_data from performance_schema.data_locks where thread_id = 47 order by object_name;+-------------+-----------+---------------+-------------+------------------------+| object_name | lock_type | lock_mode     | lock_status | lock_data              |+-------------+-----------+---------------+-------------+------------------------+| f1          | TABLE     | IX            | GRANTED     | NULL                   || f1          | RECORD    | X             | GRANTED     | supremum pseudo-record || f1          | RECORD    | X             | GRANTED     | 100, 100, 1            || f1          | RECORD    | X,REC_NOT_GAP | GRANTED     | 1                      || f1          | RECORD    | X,GAP         | GRANTED     | 101, 100, 1            || f6          | TABLE     | IS            | GRANTED     | NULL                   || f6          | RECORD    | S,REC_NOT_GAP | GRANTED     | 100, 100, 12           || f6          | TABLE     | IX            | GRANTED     | NULL                   || f6          | RECORD    | X,REC_NOT_GAP | GRANTED     | 12                     || f6          | RECORD    | X,REC_NOT_GAP | GRANTED     | 101, 100, 12           || f6          | RECORD    | S,GAP         | GRANTED     | 101, 100, 12           |+-------------+-----------+---------------+-------------+------------------------+11 rows in set (0.00 sec)

三、外键的限制:

1. 仅有 InnoDB 和 NDB 引擎支持。

2. 不支持虚拟列。

3. 不支持临时表。

4. 外键列以及引用列数据类型、字符集、校对规则都得一致。

5. 外键列以及引用列都必须建立索引。

6. 外键引用多个列的,列顺序必须一致。

7. 大对象字段不能作为引用列。

8. constraint 命名必须在单个 database 里唯一。

9. 外键级联更新操作不会触发子表上的触发器。

10. 不支持分区表。

总结

本文主要从几个例子来演示了,外键是否应该使用以及在哪些场景下使用,让大家了解外键的详细需求。

从上面我描述的几个场景来说,场景 1,2,3 很适合用外键;场景 4,5,6,7 就不太适合用外键;可以把外键功能放在数据库之外实现。


关于 MySQL 的技术内容,你们还有什么想知道的吗?赶紧留言告诉小编吧!

3b17616d6b3bbe830b44108a6705a724.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/365630.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python重启路由器_Python3控制路由器——使用requests重启极路由.py

通过本文给大家介绍Python3控制路由器——使用requests重启极路由.py的相关知识&#xff0c;代码写了相应的注释&#xff0c;以后再写成可以方便调用的模块。用fiddler抓包可以看到很多HTTP头&#xff0c;经过尝试发现不是都必须的。Upgrade-Insecure-Requests:1,#必要项&#…

从Ant Build演进Gradle Build:导入Ant Build文件

在大型项目上更改构建系统可能很困难并且需要大量工作。 幸运的是&#xff0c;对于那些将Ant版本迁移到Gradle版本的人&#xff0c;Gradle提供了特别方便的机制来促进这种迁移 。 由于Gradle基于Groovy构建&#xff0c;并且Groovy通过AntBuilder包含内置的Ant支持&#xff0c;因…

洛谷——P2719 搞笑世界杯

P2719 搞笑世界杯 题目描述 随着世界杯小组赛的结束,法国,阿根廷等世界强队都纷纷被淘汰,让人心痛不已. 于是有人组织了一场搞笑世界杯,将这些被淘汰的强队重新组织起来和世界杯一同比赛.你和你的朋友欣然去购买球票.不过搞笑世界杯的球票出售方式也很特别,它们只准备了两种球票…

HTML5 之 简单汇总

参考&#xff1a; HTML5的十大新特性 前端面试必备之html5的新特性 HTML5 主要内容&#xff1a;语义化、增强型表单、多媒体标签、Canvas、SVG、地理定位、拖放API、Web Worker、Web Storage、WebSocket、HTML 5 应用程序缓存 1.语义化元素 1.1结构元素 标签描述article表示与上…

个人信息管理系统代码_Thymeleaf+SpringBoot+Mybatis实现的易游网旅游信息管理系统...

项目简介项目源码&#xff1a;麻烦转发后关注JAVA梦想口服液私信回复【源码】即可获取&#xff01;本系统是基于ThymeleafSpringBootMybatis。是非常标准的SSM三大框架(SpringBoot就是一个大框架&#xff0c;里面包含了许多的东西&#xff0c;其中Spring就是最核心的内容&#…

HTML速查列表

HTML速查列表 HTML基本文档 <!DOCTYPE html> <html> <head> <title>文档标题</title> </head> <body> 可见文本... </body> </html> 基本标签&#xff08;Basic Tags&#xff09; <h1>最大的标题</h1> &l…

被问到有没有内核开发经验_一个人就是一个开发团队!成电硬核毕业生自制迷你电脑走红!...

△小视频近日&#xff0c;一段长3分多钟的小视频在B站突然爆红&#xff0c;不仅登上首页&#xff0c;获得超过200万的播放量&#xff0c;还被众多观众“膜拜”。有网友在评论区说&#xff1a;“其实这些东西吧&#xff0c;外行看起来很牛&#xff0c;但我这种内行看起来&#x…

使用英特尔性能计数器调整垃圾收集

介绍 我不得不承认我很震惊。 确实&#xff0c;当我意识到这个出现的日历帖子将涉及垃圾收集时&#xff0c;我感到非常震惊。 GC的主题引起了Java倡导者和那些认为内存管理应该是手动的人的热情。 撰写了许多文章&#xff0c;内容涉及看起来奇怪的命令行参数中的细微变化&#…

matlab2010a连接mysql_MATLAB2010a+OpenCV2.3.1+VS2010运行TLD

出现matlab不显示C编译器的原因主要还是当前Matlab版本相对于VS来说不够新&#xff0c;比如14版的肯定支持10的VS。 本文引用地址&#xff1a; http://blog.csdn.net/shanpohe/article/details/7596401 http://blog.sina.com.cn/s/blog_adfd55190101ejvr.html TLD(Tracking Lea…

理解总结篇—List、Set、Map

List是存储对象的容器&#xff0c;可以存储任意类型的对象且长度可变&#xff0c;List的存储对象是有顺序的&#xff0c;可重复的。 <1> List的接口框架 ArrayList实现类通过数组实现&#xff0c;在向集合中增加或删除时&#xff0c;需要对集合进行增容和拷贝&#xff0c…

SpringBoot+Redis使用jedis实现了对Redis基本类型操作超全工具类

这编文章主要介绍了springboot整合redis&#xff0c;使用jedis实现了对Redis基本类型操作&#xff0c;一些redis的常用命令总结到了一个公共的工具类中,其中使用了fastjson作为了序列化工具。 注&#xff1a;使用了 jdk 1.8 新特性 &#xff0c;jdk版本需要>1.8 一.添加mave…

更改span标签样式_CSS 内嵌样式

前面一节我们讲了行内样式&#xff0c;但是行内样式的缺点就是样式不能重用。例如当有好多个 标签&#xff0c;我们希望所有的 标签的样式都一致&#xff0c;那么需要在每个标签中都写一遍&#xff0c;这么会很麻烦&#xff0c;也会增加很多代码。那么为了解决这个问题&#…

js 函数节流

//es6语法export function debounce(func, delay) {let timer//返回一个函数&#xff0c;并拿到参数return function (...args) {if (timer) {clearTimeout(timer)}timer setTimeout(() > {func.apply(this, args)}, delay)} } //简单实现var debounce function(idle, act…

mysql 6安装当前密码_MySQL8.0 安装踩坑指南

就在昨天上午&#xff0c;刚为云服务器安装好Apache2.4.33和PHP7.2.4环境&#xff0c;准备再来一个最新的MySQL5.7.22。寻找5.7版本的rpm包时下到mysql80xxx.rpm&#xff0c;看人家的教程是mysql57&#xff0c;难道80是MySQL出出…出了8版&#xff0c;一搜新闻2个小时前MySQL发…

如何用Java编写最快的表达式评估器之一

当然&#xff0c;标题有点吸引人&#xff0c;但确实如此&#xff08;您当然不相信自己没有伪造自己的基准&#xff0c;但这是另一回事了&#xff09;。 因此&#xff0c;上周我正在寻找一个小型且可用的库来评估数学表达式。 我几乎直接偶然发现了这个stackoverflow帖子 。 推…

Elasticsearch环境搭建和介绍(Windows)

一、Elasticsearch介绍和安装 1.1 介绍 Elastic Elastic官网&#xff1a;https://www.elastic.co/cn/ Elastic有一条完整的产品线&#xff1a;Elasticsearch、Kibana、Logstash等&#xff0c;前面说的三个就是大家常说的ELK技术栈。 Elasticsearch Elasticsearch官网&#xff1…

python 银行业务系统程序编程写_python多线程实现代码(模拟银行服务操作流程)

1.模拟银行服务完成程序代码目前&#xff0c;在以银行营业大厅为代表的窗口行业中大量使用排队(叫号)系统&#xff0c;该系统完全模拟了人群排队全过程&#xff0c;通过取票进队、排队等待、叫号服务等功能&#xff0c;代替了人们站队的辛苦。排队叫号软件的具体操作流程为&…

vue 新版本 webpack 代理 跨域设置

旧版本中&#xff1a;dev-server.js 这段去掉 var apiRoutes express.Router() //getList apiRoutes.get(/getDiscList, function (req, res) {var url https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_by_tag.fcgaxios.get(url, {headers: {referer: https://c.y.qq.com…

人月神话(2)

我不知道为什么作者要拿外科医生举例子&#xff0c;在我眼里足球队更合适&#xff0c;或者说更贴近生活&#xff0c;让人们更容易理解。人的专业水平&#xff0c;在刚开始其实没什么可比性&#xff0c;试问&#xff1a;一个有小学学历的人和一个有初中学历的人在大公司招聘时有…

mysql查询出过去一个月_Mysql查询今天、昨天、7天、近30天、本月、上一月 数据...

今天select * from 表名 where to_days(时间字段名) to_days(now());昨天SELECT * FROM 表名 WHERE TO_DAYS( NOW( ) ) - TO_DAYS( 时间字段名) < 17天SELECT * FROM 表名 where DATE_SUB(CURDATE(), INTERVAL 7 DAY) < date(时间字段名)近30天SELECT * FROM 表名 where…