疫情跳广场舞?别忘啦继续学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吧!

全部评论

相关推荐

点赞 评论 收藏
转发
点赞 收藏 评论
分享
牛客网
牛客企业服务