MySQL数据库是常见的两个瓶颈是CPU和I/O的瓶颈,CPU在饱和的时候一般发生在大量数据进行比对或聚合时。磁盘I/O瓶颈发生在装入数据远大于内存容量的时候,如果应用分布在网络上,那么查询量相当大的时候那么平瓶颈就会出现在网络上。而出现上诉情况最常见的就是慢查询导致。本文就将对慢查询优化进行简单介绍,一是介绍慢查询的分析语句EXPLAIN,一是通过部分常见的慢查询进行分析讲解,希望读完后能对查询优化有所提高。
EXPLAIN命令用于SQL语句的查询执行计划(QEP)。这条命令的输出结果能够让我们了解MySQL 优化器是如何执行SQL 语句的。这条命令并没有提供任何调整建议,但它能够提供重要的信息帮助我们做出优化决策。
EXPLAIN结果集为id,select_type,table,type,possible_keys,key,key_len
,ref,rows,Extra,本文主要针对select_type,type,ref,Extra进行讲解。
select_type 列提供了各种表示table 列引用的使用方式的类型。最常见的值包括SIMPLE、PRIMARY、DERIVED 和UNION。其他可能的值还有UNION RESULT、DEPENDENT SUBQUERY、DEPENDENT UNION、UNCACHEABLE UNION 以及UNCACHEABLE QUERY。
- SIMPLE:对于不包含子查询和其他复杂语法的简单查询,这是一个常见的类型。(这个也是最理想的)
- PRIMARY:这是为更复杂的查询而创建的首要表(也就是最外层的表)。这个类型通常可以在DERIVED 和UNION 类型混合使用时见到。
- DERIVED:当一个表不是一个物理表时,那么就被叫做DERIVED。一般代表生成了临时表或子查询,建议优化。
- UNION:这是UNION 语句其中的一个SQL 元素。代表使用了UNION,建议优化
type这是最重要的字段之一,显示查询使用了何种类型。从最好到最差的连接类型为system、const、eq_reg、ref、range、index和ALL:
- system、const:可以将查询的变量转为常量. 如id=1; id为 主键或唯一键.
- eq_ref:访问索引,返回某单一行的数据.(通常在联接时出现,查询使用的索引为主键或惟一键)
- ref:访问索引,返回某个值的数据.(可以返回多行) 通常使用=时发生
- range:这个连接类型使用索引返回一个范围中的行,比如使用>或<查找东西,并且该字段上建有索引时发生的情况(注:不一定好于index)
- index:以索引的顺序进行全表扫描,优点是不用排序,缺点是还要全表扫描
- ALL:全表扫描,应该尽量避免
前面简单介绍了EXPLAIN的结果集,下面将通过生产实际的慢查询来进行优化讲解。
案例1:
执行顺序通过ID判断,从大到小,首先从select_type为DERIVED,type为index,Extra为NULL可得出该查询对yyd_advert_activation_record进行了全表查询,然后从select_type为PRIMARY,table为<derived2>,type为all,Extra为Using temporary; Using filesort可得出查询使用子查询,生成了临时表并排序。
出现这种语句,首先线上不应该有这种全量统计类的需求,而且并发较高,这个对线上会造成极大的压力。其次该语句的语法上没有优化空间,从数据上看,这个张表从17年1月就没有数据更新,这个查询每次查询数据一致,所以这个查询需求联系开发删除。
案例2:
该查询select_type为SIMPLE,possible_key为index_user_id可以看出user_id上有索引,但是type为all,Extra为Using where可以看出这个查询实际是全表扫描USER_ID LIKE '23930525',仔细分析USER_ID的数据类型为BIGINT(20),就可知该查询问题在于使用了like,使得USER_ID转换为varchar类型进行匹配,导致索引无法使用。该查询优化建议为将like改为=
案例3:
从结果集可以看出这个查询使用了两次子查询,并且在第二个子查询中使用了临时表排序,虽然rows显示查询量不大,但实际查询还是全表扫描。该查询实际执行需要14秒,扫描17W+数据。
仔细分析该查询,两个子查询关联条件和表都一致,第二个子查询只是进行了group by和distinct排序和聚合,但是结果fushu_id又是表的关联条件,所以第二个子查询完全没有必要,直接修改为如下即可。查询优化后只需0.004s。
案例4:
虎哥:
我们这边要在对账库上进行insert select, 但是发现这条语句效率极低,已经执行2小时以上,根本不出结果
INSERT into mt_M_direct_1807_A11 SELECT * from mt_M_direct_1807_A1 a GROUP BY a.`订单id` HAVING COUNT(a.`订单id`) >1; 很慢, 将这条语句拆解成
请DBA帮忙查一下原因
我:
SELECT a.* from mt_M_direct_1807_A1 a GROUP BY a.`订单id` HAVING COUNT(a.`订单id`) >1; 这个也很慢,但是执行
SELECT a.`订单id` from mt_M_direct_1807_A1 a GROUP BY a.`订单id` HAVING COUNT(a.`订单id`) >1 这个就很快,
优化后,insert执行,只需要20S的时间,可以将语句优化成:
insert into mt_M_direct_1807_A11 SELECT b.* from mt_M_direct_1807_A1 b where b.`订单id` FROM (SELECT a.`订单id` from mt_M_direct_1807_A1 a GROUP BY a.`订单id` HAVING COUNT(a.`订单id`) >1);
以上只是比较典型的慢查询优化案例,线上还有很多类型的情况导致慢查询的产生从而影响服务器性能和程序效率,希望今后在开发SQL的时候能够多考虑相关情况,可通过EXPLAIN来进行判断是否查询性能有问题,编写优质代码。