对于mysql性能优化,除了宏观层面的网络、设备等优化,sql语句的优化是极为重要的一环,需要想办法找到对应的执行效率低的sql语句进行优化。
慢查询日志是定位低效率sql的手段之一,通过以下命令,设置开启慢查询日志。
#显示是否开启了慢查询日志
show variables like 'slow_query_log';
#开启慢查询日志
set global slow_query_log = on
#慢查询日志输出位置
set global slow_query_log_file = '/var/lib/mysql/gupaoedu-slow.log'
set global log_queries_not_using_indexes = on
#设置耗时查询时间阈值
set global long_query_time = 0.1
有了慢查询日志之后,可以通过mysqldumpslow分析哪一条sql语句需要进行优化,建立合适的索引。
使用阿里的Druid数据源时可以对接自带的监控平台,非常方便有各种统计数据,可以监控慢sql
这个就非常粗暴了,只管堆数据,然后看接口的效率,可以粗略定位sql语句
explain输出格式含义,这一个在mysql官网有完整的解释,奈何英语水平有限,看的难受,幸好发现这位博主的一篇文章,解释的非常全面,易懂,强烈推荐EXPLAIN用法和结果分析,了解的explain的输出含义,才能做以下分析。
深入了解explain结果分析,了解每种情况会出现什么样的explain结果输出,以此能够更加清晰明了、正确地使用索引。
假设有如下表结构,创建主键为id
,联合索引为(biz_no,cus_code)
,单列唯一索引uni_no
-- auto-generated definition
create table index_test
(
id int auto_increment
primary key,
biz_no varchar(11) default '' not null,
cus_code varchar(11) default '' not null,
uni_no varchar(11) default '' not null,
constraint idx_biz_no_cus_code
unique (biz_no, cus_code),
constraint uk_uni_no
unique (uni_no)
);
执行以下存储过程,插入数据。
#定义存储过程
delimiter //
DROP PROCEDURE IF EXISTS insert_test_val;
##num_limit 要插入数据的数量,begin_num 起始数字
CREATE PROCEDURE insert_test_val(in num_limit int,in begin_num int)
BEGIN
DECLARE i int default 1;
DECLARE a varchar(11) default '';
DECLARE b varchar(11) default '';
DECLARE c varchar(11) default '';
WHILE i<=num_limit do
set a = concat('bizno',begin_num);
set b = concat('cus',begin_num);
set c =concat('uni',begin_num);
INSERT into index_test(biz_no, cus_code, uni_no) values (a,b,c);
set i = i + 1;
set begin_num = begin_num + 1;
END WHILE;
END
#调用存储过程
call insert_test_val(200000,1);
在where条件中不同的使用方式可能会对索引的有效性有不同的影响,这里简单分析几种情况。
对于单列索引,最常见的是key=?的情况,例如针对上面的表结构,执行以下sql,分析explain结果。
explain select * from index_test where uni_no='uni500';
可以看出,type是const,表示是一次索引就能得到数据,索引有效。
如果我们了解innodb索引树的结构,应该可以知道,这里的索引过程应该是,先从辅助索引树uni_no
的索引树检索得到主键id,然后还需要从主键索引树检索目标记录,因此除了能够使用索引之外,还可以进一步优化,就是根据业务需求,看能否使用上覆盖索引
。
覆盖索引下
例如以下sql。只选择id的话,在辅助索引树就能完成,不需要再去主键索引树检索,大大提高了性能。
explain select id from index_test where uni_no='uni500';
对于范围查询,会影响查询效率,这个是必然的,但是在范围条件中建立索引,能否够提升查询效率?
索引生效
执行如下sql,使用索引进行范围查询
select * from index_test where uni_no < 'uni10000';
结果是下图,只有四条记录。
执行explain sql分析执行计划,结果如下图,可以看出,这里的type使用的是range类型,也就是从指定的范围开始检索记录,uni_no
索引生效。
反转,索引不生效
但是假如我们执行以下sql,把条件改为大于号,得到的explain输出确实不一样。
explain select * from index_test where uni_no > 'uni10000';
输出,此时执行的是全表扫描,索引不生效,这是为什么?
思考,为何范围查询索引时而生效时而不生效
我在数据库表中插入总共20万数据,其中条件为uni_no < 'uni10000'
的数据只有四条。而反过来的数据则有199996条,约等于全表数据。我们可以知道在不是覆盖索引的情况下,使用辅助索引树检索数据需要检索两遍,假如mysql在uni_no
索引树范围内逐条检索到199996条主键记录符合,然后每条主键记录都去主键索引树检索记录数据的话,效率还不如全表扫描。
因此执行计划对此有相应优化,如果查询优化器预计辅助索引树检索到的符合的记录量特别大的情况下,就不会使用辅助索引树查询,反而使用全表扫描来完成检索,例如uni_no > 'uni10000'
的情况。反之,如果预计辅助索引树检索到的记录不多的情况下,还是会使用辅助索引来检索,例如uni_no < 'uni10000'
的情况.
但是需要注意,非唯一索引树精确值等值检索,不管匹配多少记录,都是走索引树,不会因为匹配多了就不走索引树检索,猜测是因为非唯一索引树精确检索效率很高,type属于ref类型,因此先检索辅助索引树在检索主键索引树的效率还是比all全表要高。
再次思考,在单列覆盖索引的情况下,是否应该一直生效
根据上面的思考再次推论,在覆盖索引的情况下,检索数据不需要在辅助索引树和主键索引树分别检索两遍,只需要在辅助索引树检索就能得到对应数据,那么直接在辅助索引树上进行查询应该是最优的策略,推测不管是uni_no < 'uni10000'
还是uni_no > 'uni10000'
,索引都应该生效。
执行以下sql验证。
explain select id from index_test where uni_no > 'uni10000';
explain select id from index_test where uni_no < 'uni10000';
结果查询的type都是range,索引生效,推测正确。
类比,其他索引时而生效时而不生效的情况
在key > ? | key < ?情况下,索引是否生效并不是绝对的。因为是范围查询,导致辅助索引树可能匹配到多条记录,然后到逐渐索引树检索,这种情况索引就不会生效,反而会使用all查询。其实类似的情形可以类比到一些辅助索引树匹配到记录比较多的情况。
order by或者group by索引时而生效时而不生效
例如本例子中,对比以下两个sql的输出,你会发现也是索引不一定生效的,type不一定是range,一切有查询优化器决定。
explain select * from index_test where biz_no > 'bizno10000' order by biz_no,cus_code;
#辅助索引匹配记录预计趋近全表,不使用索引
#输出,使用全表扫描,filesort外部排序
1 SIMPLE index_test ALL idx_biz_no_cus_code 199586 Using where; Using filesort
#但是假如是覆盖索引,辅助树匹配记录预计趋近全表,则还是会使用索引排序,如下sql
explain select biz_no from index_test where biz_no > 'bizno10000' order by biz_no,cus_code;
#输出,使用range,索引排序
1 SIMPLE index_test range idx_biz_no_cus_code idx_biz_no_cus_code 13 99793 Using where; Using index
explain select * from index_test where biz_no < 'bizno10000' order by biz_no,cus_code;
#辅助索引匹配记录预计较少,
#输出,使用range,索引排序
1 SIMPLE index_test range idx_biz_no_cus_code idx_biz_no_cus_code 13 4 Using where
order by或者group byPS分析总结:当order by出现filesort时考虑以下问题
当然,出现filesort并不是说就一定是大问题,如果select的explain 输出type是range以上,使用filesort也是可以接受的,只是如果对性能有很高要求时,可以考虑这样子去优化。
##这是一个查询类型是range,但是使用filesort的sql,效率也不差
explain select * from index_test where uni_no < 'uni10000' order by biz_no,cus_code;
#输出
1 SIMPLE index_test range uk_uni_no uk_uni_no 13 4 Using where; Using filesort
这些情况下不走索引,explain 输出type都是all
单列索引有效性分析总结以下几点:
精确匹配索引生效
范围查找情况,索引生效判断依据以下规则:
覆盖索引下,范围查找索引一律生效
非覆盖索引查询,如果预计辅助索引树检索范围内匹配记录数据量较大,则索引不生效,做All扫描;
如果预计索引树检索范围内匹配记录数据量较小,则索引生效,做range类型查询。
order by、group by等没有使用索引排序出现filesort,参考PS分析总结
like的情况按照最左匹配原则生效
没使用索引精确或者范围查询,索引不可能生效。非等查询,索引也不能生效。
分析联合索引的有效性时,首先需要知道的点是联合索引的匹配原则是最左匹配。
假如有联合索引(key1,key2),那么在完全精确匹配的情况下,索引是有效的,查询类型type是const,表示一次索引就能得到结果。例如以下例子。
explain select * from index_test where biz_no='bizno5000' and cus_code='cus5000';
输出如图所示
类似地,两列以上的联合索引精确匹配也是const类型查询。
假如有联合索引(key1,key2),在没有精确匹配的情况下只使用key1=?作为索引条件,遵循最左匹配原则,只用最部分最左条件进行索引查询,索引生效情况如何?以下例子。
explain select * from index_test where biz_no='bizno5000';
结果输出如图下所示,可以看出查询类型下降一个级别,变成ref,索引可以匹配多条记录,但是效率依然很高,索引依然生效。
类似地,两列以上的联合索引,依据最左前缀原则匹配查询,查询type也是ref。例如联合索引(key1,key2,key3),使用where条件是key1=‘a’ and key2=‘b’,查询type也是ref。
类似地,假如最左列使用精确检索,右边列使用范围检索,精确查询效果如何?
explain select * from index_test where biz_no = 'bizno10000' and cus_code >= 'cus10000';
输出查询类型为ref
,索引生效.
跟单列索引一样,涉及到仅使用范围查询分析索引生效情况,必须分为是否是覆盖索引的查询来分析。要想仅使用范围查询,并且联合索引生效,还是需要按照最左前缀匹配,所以只有在最左列作为范围条件时才可能生效。因此最终的分析方式其实跟单列的索引范围查询一样分析。
辅助索引树匹配范围记录较少的情况?
使用最左列进行范围查询, biz_no < 'bizno10000’记录较少,只有四条。
explain select * from index_test where biz_no < 'bizno10000' ;
输出结果如下图所示,虽然使用了范围查询,但是辅助索引树预计范围内匹配记录数量较少,那么再次扫描主键索引树获取全部数据的消耗时间不会很高,因此还是使用查询类型type是range的查询。此时索引生效。
辅助索引树匹配范围记录较多的情况?
使用最左列进行范围查询, biz_no > 'bizno10000’记录趋近全表。
explain select * from index_test where biz_no > 'bizno10000' ;
输出结果如下图所示,联合索引的索引树预计范围内匹配的数据量比较大,再次扫描主键索引树获取全部数据的消耗时间可能较高,所以查询优化器优化决定使用全表扫描代替range查询。此时索引不生效。
覆盖索引下,减少了再次扫描主键索引树的过程,那么其实不管联合索引的索引树匹配多少条记录都是无所谓的,因此都是使用range查询,索引都会生效。
例如匹配记录多的情况。
explain select biz_no from index_test where biz_no > 'bizno10000' ;
输出如下图所示,虽然预计扫描行数较多,但是还是使用range查询,索引生效,因为不需要再次扫描主键索引树,range查询联合索引树效率最高。Extra中的Using Index使用覆盖索引查询。
不用说,匹配记录少的情况同理也是一样。
联合索引的索引生效原则如下: