(1)背景:
上次的案例主要问题在于MySQL内部自动使用了半连接优化,结果半连接的时候导致大量无索引的全表扫描,引发了性能下降,而这次的案例,其实也是类似,是MySQL数据库在选择索引的 时候,选择了不太合适的索引,导致了性能极差,引发了慢查询。
先从当时线上的商品系统出现的一个慢查询开始讲起,某一天晚上,线上数据库频繁报警,这个报警的意思大致是说,数据库突然涌现了大量的慢查询,而且因为大量的慢查询,导致每一个 数据库连接执行一个慢查询都要耗费很久。
那这样的话,必然会导致突然过来的很多查询需要让数据库开辟过多的连接,因此这个告警也告诉我们,数据库的连接突然暴增,而且每个连接都打满,每个连接都要执行一个慢查询,慢查询 还跑的特别慢,接着引发的问题,就是数据库全部打满,没法开辟新的连接,但是还持续的有新的查询发送过来,导致数据库没法处理新的查询,很多查询发到数据库直接就则阻塞超时了,这也直接导致 线上的商品系统频繁的报警,出现了大量的数据库查询超时报错的异常。
这种情况基本意味着商品数据库以及商品系统濒临崩溃,大量慢查询耗尽了数据库的连接资源,而且一直阻塞在数据库里执行,数据库没法执行新的查询,商品数据库没法执行查询,用户没法 使用商品系统,也就没法查询和筛选电商网站里的商品了。
而且正值晚高峰,也就是一个电商网站比较频繁的时候,虽说商品数据是有多级缓存架构的,但是实际在下单等待过程中,还是会大量的请求商品系统的,所以晚高峰,商品系统本身TPS大致 是在每秒几千的,因此这个时候发现数据库的监控显示,每分钟的慢查询超过了10W+,也就是商品系统大量的查询都变成了慢查询。那么慢查询的都是一些什么语句?其实是下面的语句,做了一个简化:<br /> select * from products where category='xx' and sub_category='xx' order by id desc limit xx,xx<br /> 这其实是一个平常的SQL语句,就是用户在电商网站上根据商品的品类以及子类在进行筛选,当然真实的SQL语句里,可能还包含其他的一些字段的筛选,比如什么品牌以及销售属性之类的,然后按照id倒序排序,最后是分页。<br /> 这个语句执行的商品表里大致1亿左右的数据量,这个量级已经稳定了很长时间了,主要也就是这么多商品,但是上面的那个SQL居然一执行就是几十秒。基本上数据库的连接全部被慢查询打满,一个连接执行几十秒的SQL,然后才能执行下一个SQL,此时数据库基本就废了,没法执行查询。<br /> 所以商品系统本身也大量的报警说查询数据库超时异常了。
(2)执行计划分析:
分析下SQL执行很慢的原因,select * from products where category=’xx’ and sub_category=’xx’ order by id desc limit xx,xx
这个表当时肯定是对经常使用到的查询字段都建立索引,那么针对这里简化的SQL语句,可以认为索引如此:KEY index_category(category,sub_category) 肯定是存在的,所以基本上可以确认上面的SQL绝对是可以用上索引的。
因此如果你一旦用上索引,那么按品类和子类去索引里筛选,筛选速度是很快的,筛选出来的数据是不多的,按照这个语句应该执行的速度是很快的,即使表有亿级数据,但是执行时间也不应该超过1s。但是这个SQL语句跑了几十秒,那说明他肯定就没用我们建立的那个索引,所以才会这么慢,那么他到底怎么执行的呢?来看看他的执行计划:
explain select from products where category=’xx’ and sub_category=’xx’ order by id desc limit xx,xx
最核心的信息,他的possible_key 里是我们的index_category,结果实际用的key不是这个索引,而是PRIMARY!!,而且Extra里清晰写了Using where,到此为止,这个SQL性能为什么这么差,就知道了,其实本质上就是在主键的聚簇索引上进行了扫描,一边扫描一边还用了where 条件里的两个字段进行筛选,所以这么扫描很耗费时间。因此此时为了快速解决这个问题,就需要强制性的改变MySQL自动选择这个不合适的聚簇索引进行扫描的行为,那么怎么改变?使用force index语法,如下:
select from products force index(index_category) where category=’xx’ and sub_category=’xx’ order by id desc limit xx,xx
使用上述语法之后,强制SQL语句使用指定的索引,此时再次执行这个SQL语句,会发现仅仅耗费100多毫秒而已! 性能得到提升。
因此当时在紧急关头中,一下子就把问题解决了,这里也告诉大家一个实战技巧,就是如何强制概念MySQL的执行计划,面试:如果MySQL使用了错误的执行计划怎么办?很简单强制指定索引。
(3)遗留问题:
为什么在这个案例中MySQL默认会选择对主键聚簇索引进行扫描?
为什么没使用index_category这个二级索引进行扫描?
即使用了聚簇索引,为什么这个SQL以前没有问题,现在突然就有问题了?
问题:key用到primary效率很低嘛?除了看key之外,还可以通过extra字段来判断执行效率?
解答:select_type为primary表示查询包含了复杂的子查询,是的,看explain使用索引的同时还需要看extra
开启慢查询的日志,可以看到执行慢的SQL