疫情跳广场舞?别忘啦继续学MySQL!!
大家好,我是亚索!
最近大连新一轮疫情又席卷而来,某高校封校后青年男女们竟在宿舍跳起了广场舞?!
疫情之下,大家仍不忘进行一些丰富的娱乐文化生活,亚索提醒大家在开心happy的同时,可不要忘了自身的工作和科研任务哦!
正题
今天继续来介绍有关MySQL的一些干货吧!主要内容有MySQL一条Sql语句执行很慢的原因?查询语句索引失效的原因?。
这是后端Java面试官最爱问知识之一,据说也是 腾讯面试问过的真题 哈!知识介绍均来自互联网搜集资料以及课本笔记,如有重复涉及版权问题,纯属偶然,还请私信纠正一下哈。
Sql执行很慢原因:分类讨论!
亚索觉得该问题属于开放性范畴,就像百度曾经问过的面试题:浏览器输入url回车之后,会发生什么? 细说起来会涉及到计算机网络的很多知识,sql语句执行很慢也能涉及到MySQL很多核心知识。
遇到问题不要慌张,首先理清楚逻辑,理性思考,亚索觉得这个问题则需要分类讨论:
-
在客观条件(数据量等)不变的情况下,该Sql语句一直执行的很慢。
-
该Sql语句在大多数情况下执行速度正常,在少数情况出现执行过慢的问题。
Sql执行一直很慢的情况
在MySQL数据量不变的情况下,Sql语句执行一直很慢,那么可能就是Sql语句书写的问题或者索引命中存在问题了。
比如有一个表,建表语句如下:
mysql> CREATE TABLE `t_da_age` ( `id` int(11) NOT NULL, `no` int(11) DEFAULT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB;
Sql查询语句如下:
select * from t_da_age where 30 <age
and age < 50;
那么这条语句查询效率慢,原因可能是:
1、age字段上并未建立索引,此时数据库则会自动走全表扫描,效率很低,很慢。
2、如果age字段上加了索引,但是由于一些错误的Sql书写方式,又会导致索引失效,数据库还是自动走全表扫描,比如语句为:
select * from t_da_age where age - 1 = 100;
select * from t_da_age where abs(age) = 30;
//假设abs是求绝对值的函数,以上无法命中索引
这种对索引字段在符号左边进行计算,或者进行函数操作的方式,是无法命中索引,亦称为索引失效。但若是"where age = 1+100",这种是可以命中索引的。
另外,可以通过在sql语句前加上explain命令这种方式查看sql语句是否用到索引。
3、更深层次地,还有一个原因就是数据库自己不选索引,走全表扫描!或者选错了别的索引!
这个也可以归结为数据库索引失效情况之一,但这是数据库自己本身执行语句时出现了问题,不是Sql语句书写的毛病,比如:
select * from t_da_age where 30 < age and
age < 50;
这种情况下,就算age字段上建立了索引,数据库系统也不一定会走该索引,不一定是先查到age对应B+树的叶子节点中主键的值,再通过主键索引查询需要的整行数据返回(主键索引与非主键索引的区别,以后会介绍)。
系统是有可能直接扫描全表,找出符合"30 < age and age < 50"的整行数据。
数据库计算索引基数,存在采样失误
系统在执行这条语句的时候,会进行预测,即在走age字段索引,需要扫描多少行数据,如果预测到扫描行数过多,系统就可能不走索引而直接走全表扫描了。
数据库系统是通过遍历索引字段的部分数据,即通过 采样统计 的方式,预测计算出索引的基数。基数也叫做区分度,一个索引字段在一定范围内不同的值越多,意味着出现相同数值的索引越少,索引区分度就越高,即意味着age在30-50这个范围内的数据行数就越少。
假如age字段的基数(区分度)实际上是很大的,但索引系统在采样统计的时候,恰巧遍历了基数很小的那一部分数据(即重复率较高),然后就误以为该索引基数很小,在查询时数据库觉得走全表扫描更加高效,同样会导致最终不选索引,索引失效!!
注意:查询语句是否需要排序、是否需要使用临时表也是会影响系统选择的
有时候可以通过强制走索引的方式查询数据,比如:
select * from t force index(a) where age
< 50 and age > 30;
或者可以用这条语句重新统计索引的基数:
analyze table t_da_age;
也可以查看索引字段的基数等相关信息:
show index frow t_da_age;
既然数据库会算错索引的基数,如果查询语句有涉及多个索引的时候,系统也有可能会选错走别的索引,这也同样可能会导致Sql执行很慢!
补充总结一下索引失效的原因有哪些:
-
where查询索引字段在符号左边有计算或者对索引字段进行函数计算
-
数据库对每个索引通过采样计算区分度,可能产生误采样导致认为该索引区分度小,走该索引查询不如走全表扫描更快,最终选择不走索引
-
like模糊查询中有以%开头
-
联合索引使用时未遵守最左前缀原则,where未用左列字段或者左边连续几列字段,比如a、b、c是联合索引,查询条件为where b=3,也会索引失效
-
where no = 3 or id = 4查询条件有or,必须所有列全建立索引,否则也会索引失效
Sql执行偶尔很慢的情况
一条Sql语句执行偶尔很慢,亚索觉得这个语句可能是没有什么问题的,那原因主要有两个。
1、语句执行时,数据库正在刷脏页
当数据库插入或者更新一条数据的时候,我们知道数据库会在内存中把对应字段的数据更新了,但是更新之后,并不会马上同步持久化到磁盘中去,而是把这些更新的记录写入到 redo log 日志中去,等到空闲的时候,再通过 redo log 把最新的数据更新到磁盘中去。
当内存数据页跟磁盘数据页内容不一致的时候,该内存页称为"脏页"。内存数据写入到磁盘后,内存和磁盘上的数据页的内容就一致了,称为"干净页"。
数据库通过 redo log 将数据更新到磁盘中去的操作可以称为"刷脏页"。刷脏页有很多种场景,常见有4种,性能方面主要关注前2种:
-
内存不够用:如果一次查询较多的数据,恰好碰到所查数据页不在内存中时,需要申请内存,而此时恰好内存不足,就需要淘汰一部分内存数据页,如果是干净页,就直接释放,如果恰好是脏页就需要刷脏页
-
redo log不够用:redo log 里的容量是有限的,如果数据库更新频繁,redo log 很快就会被写满了,这个时候不能等到空闲的时候再把数据同步到磁盘的,只能暂停其他操作,全身心来把数据同步到磁盘中去的,这就会导致我们平时执行正常的SQL语句突然执行很慢
-
MySQL 认为系统“空闲”的时候
-
MySQL 正常关闭的时候:MySQL会把内存的脏页都 flush 到磁盘上,这样下次 MySQL 启动的时候,就可以直接从磁盘上读数据。
2、Sql执行时遇到锁,如表级锁、行级锁
刚好这条语句涉及到的表,别的语句或者线程在用,并且加锁了,拿不到锁,只能慢慢等待别人释放锁了(表级锁)。或者,表没有加锁,但要使用到的某一行被加锁了(行级锁)。
如果要判断是否真的在等待锁,我们可以用show processlist这个命令来查看当前的状态。
小结
本文主要介绍了一下MySQL中一条Sql语句执行很慢的原因,大家如果在面试中被问到,一定要记得分类讨论哦!
总结一下,一条Sql语句执行很慢,分类讨论:
1、这条 SQL 语句一直执行的很慢,则有如下原因
-
没有用上索引:例如该字段没有索引;由于对字段进行运算、函数操作等导致索引失效。
-
数据库基数的采样计算有误,选错了索引或者不选索引。
2、大多数情况下很正常,偶尔很慢,则有如下原因
-
数据库正在刷脏页,例如 内存不够用了或者 redo log 写满了需要同步到磁盘。
-
执行Sql的时候,遇到锁,如表级锁或者行级锁。
大家面试的时候,说完提纲要点以后,亚索觉得最好能详细展开陈述哈!说的越深,面试加分越多!
好了今天就说到这,我是亚索,关注下面vx公众号,大家一起学Java吧!