性能分析工具——EXPLAIN
1、概述
定位了查询慢的SQL之后,我们就可以使用EXPLAIN或DESCRIBE工具做针对性的分析查询语句 。 DESCRIBE语句的使用方法与EXPLAIN语句是一样的,并且分析结果也是一样的。
MySQL中有专门负责优化SELECT语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Query提供它认为最优的执行计划(他认为最优的数据检索方式,但不见的是DBA认为最优的,这部分最耗费时间)。
这个执行计划展示了接下来具体执行查询的方式,比如多表连接的顺序是什么,对于每个表采用什么访问方法来具体执行查询等等。MySQL为我们提供了EXPLAIN语句来帮助我们查看某个语句的具体执行计划,大家看懂EXPLAIN语句的各个输出项,可以有针对性的提升我们查询语句的性能。
1.1、能做什么?
- 表的读取顺序
- 数据读取操作的操作类型
- 哪些索引可以使用
- 哪些索引被实际使用
- 表之间的引用
- 每张表有多少行被优化器查询
1.2、版本情况
- MySQL 5.6.3以前只能EXPLAIN SELECT;MySQL 5.6.3以后就可以EXPLAIN SELECT,UPDATE,DELETE
- 在5.7以前的版本中,想要显示pratitions需要使用explain partitions命令;想要显示filtered需要使用explain extended命令。在5.7版本后,默认explain直接显示partitions和filtered中的信。
2、基本语法
EXPLAIN和DESCRIBE语句的语法形式如下:
EXPLAIN SELECT * FROM `sys_user`
或者
DESCRIBE SELECT * FROM `sys_user`
EXPLAIN语句输出的各个列的作用如下:
列名 | 描述 |
---|---|
id | 在一个大的查询语句中每个SELECT关键字都对应一个唯一的id |
select_type | SELECT 关键字对应的那个查询类型 |
table | 表名 |
partitions | 匹配的分区信息 |
type | 针对单表的访问方法 |
possible_keys | 可能用到的索引 |
key | 实际用到的索引 |
key_len | 实际使用到的索引长度 |
ref | 当使用索引列等值查询时,与索引列进行等值匹配的对象信息 |
rows | 预估的需要读取的记录条数 |
filtered | 某个表经过搜索条件过滤后剩余记录条数的百分比 |
Extra | 一些额外的信息 |
3、数据准备
#创建表
CREATE TABLE s1 (
id INT AUTO_INCREMENT,
key1 VARCHAR(100),
key2 INT,
key3 VARCHAR(100),
key_part1 VARCHAR(100),
key_part2 VARCHAR(100),
key_part3 VARCHAR(100),
common_field VARCHAR(100),
PRIMARY KEY (id),
INDEX idx_key1 (key1),
UNIQUE INDEX idx_key2 (key2),
INDEX idx_key3 (key3),
INDEX idx_key_part(key_part1, key_part2, key_part3)
) ENGINE=INNODB CHARSET=utf8;
CREATE TABLE s2 (
id INT AUTO_INCREMENT,
key1 VARCHAR(100),
key2 INT,
key3 VARCHAR(100),
key_part1 VARCHAR(100),
key_part2 VARCHAR(100),
key_part3 VARCHAR(100),
common_field VARCHAR(100),
PRIMARY KEY (id),
INDEX idx_key1 (key1),
UNIQUE INDEX idx_key2 (key2),
INDEX idx_key3 (key3),
INDEX idx_key_part(key_part1, key_part2, key_part3)
) ENGINE=INNODB CHARSET=utf8;
#创建存储函数:
DELIMITER //
CREATE FUNCTION rand_string1(n INT)
RETURNS VARCHAR(255) #该函数会返回一个字符串
BEGIN
DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
DECLARE return_str VARCHAR(255) DEFAULT '';
DECLARE i INT DEFAULT 0;
WHILE i < n DO
SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
SET i = i + 1;
END WHILE;
RETURN return_str;
END //
DELIMITER ;
SET GLOBAL log_bin_trust_function_creators=1;
#创建存储过程:
DELIMITER //
CREATE PROCEDURE insert_s1 (IN min_num INT (10),IN max_num INT (10))
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO s1 VALUES(
(min_num + i),
rand_string1(6),
(min_num + 30 * i + 5),
rand_string1(6),
rand_string1(10),
rand_string1(5),
rand_string1(10),
rand_string1(10));
UNTIL i = max_num
END REPEAT;
COMMIT;
END //
DELIMITER ;
DELIMITER //
CREATE PROCEDURE insert_s2 (IN min_num INT (10),IN max_num INT (10))
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO s2 VALUES(
(min_num + i),
rand_string1(6),
(min_num + 30 * i + 5),
rand_string1(6),
rand_string1(10),
rand_string1(5),
rand_string1(10),
rand_string1(10));
UNTIL i = max_num
END REPEAT;
COMMIT;
END //
DELIMITER ;
#调用存储过程
CALL insert_s1(10001,10000);
CALL insert_s2(10001,10000);
SELECT COUNT(*) FROM s1;
SELECT COUNT(*) FROM s2;
4、EXPLAIN各列作用
为了更好的理解,我们调整下EXPLAIN输出列的顺序。
1、table
不论我们的查询语句有多复杂,里边包含了多少个表,到最后也是需要对每个表进行单表访问的,所以MySQL规定EXPLAIN语句输出的每条记录都对应着某个单表的访问方法,该条记录的table列代表着该表的表名(有时不是真实的表名字,可能是简称)。
EXPLAIN SELECT * FROM `sys_user`
这个查询语句只涉及对s1表的单表查询,所以EXPLAIN输出的只有一条记录,其中的table列的值是sys_user,表明这条记录的是用来说明sys_user表的单表访问方法的。
2、id
select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序。
- id如果相同,可以认为是一组,从上往下顺序执行
- 在所有组中,id值越大,优先级越高,越先执行
- 关注点:id号每个号码,表示一趟独立的查询,一个sql的查询趟数越少越好
3、select_type
4、type(重点)
执行计划的一条记录就代表着MySQL对某个表的执行查询时的访问方法,又称“访问类型”,其中的type列就表明了这个访问方法的是啥,是较为重要的一个指标。比如,看到type列的值是ref,着表明MySQL即将使用ref访问方法来执行对sys_user表的查询
完整的访问方法如下:system,const,eq_ref,ref,fulltext,ref_or_null,index_merge,unique_subquery,index_subquery,renge,index,All。
详解:
-
system
当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的,比如MyISAM、Memory,那么对该表的访问就是system。
create table a(i int) ENGINE = MyISAM INSERT INTO a(i) VALUES (1) EXPLAIN SELECT * from a
-
const
当我们根据主键或者唯一二级索列与常数进行等值匹配时,对单表的访问方法就是const
EXPLAIN select * from s1 where id = 10002
-
eq_ref
在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的(如果该主键或者唯一二级索引的联合索引的话,所有的索引列都必须进行等值匹配),则对该被驱动表的访问方法就是eq_ref
EXPLAIN SELECT * FROM s1 inner join s2 ON s1.id = s2.id
-
ref
当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方式就可能是ref
EXPLAIN SELECT * FROM s1 where key1 = 'vLuQVg'
-
ref_or_null
当对普通二级索引进行等值匹配查询,该索引列的值也可以是
NULL
值时,那么对该表的访问方法就可能是ref_or_null -
index_merge
我们的 where 中可能有多个条件(或者join)涉及到多个字段,它们之间进行 OR,那么此时就有可能会使用到 index merge 技术。index merge 技术如果简单的说,其实就是:对多个索引分别进行条件扫描,然后将它们各自的结果进行合并(intersect/union)
EXPLAIN SELECT * FROM s1 where s1.key1 = 'vLuQVg' or s1.key2 = 10036
-
unique_subquery
unique_subquery
是针对在一些包含IN
子查询的查询语句中,如果查询优化器决定将IN
子查询转换为EXINST
子查询,而且子查询可以用到主键进行等值匹配的话,那么该子查询执行计划的type
列的值就是unique_subquery
-
renge
如果使用索引获取某些范围区间的记录,那么就可能使用到renge
EXPLAIN SELECT * FROM s1 where key2 in (10096,1)
-
index
当我们可以使用索引覆盖,但需要扫描全部的索引记录时,该表的访问方法就是index
EXPLAIN SELECT key_part1 FROM s1 where key_part2 = 'aABgbNKlbz'
-
all
最熟悉的全表扫描
EXPLAIN SELECT * FROM s1
小结
结果值从最好到最坏依次是:
system——>const——>eq_ref——>ref——>fulltext——>ref_or_null——>index_merge,unique_subquery——>index_subquery——>renge——>index——>All
其中比较重要的几个提取出来了。SQL性能优化的目标:至少要到达range级别,要求是ref级别,最好是const级别。(阿里巴巴开发手册要求)
5、possible_keys和key
在EXPLAIN语句输出的执行计划中,possible_keys列表示在某个查询语句中,对某个表执行单表查询时可能用到的索引有哪些。一般查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用。key列表示实际用到的索引有哪些,如果为Null,则没有使用索引。
6、ref列
这一列显示了在key列记录的索引中,表查找值所用到的列或常量,
常见的有:const(常量),字段名(例:film.id)
7、rows
这一列是mysql估计要读取并检测的行数,注意这个不是结果集里的行数。
8、filtered
某个经过搜索条件过滤后剩余记录条数的百分比
如果使用的是索引执行的单表扫描,那么计算时需要估计出满足除使用到对应索引的搜索条件外的其他搜索条件的记录有多少条
对于单表查询来说,这个filtered列的值没什么意义,我们更关注在连接查询中驱动表对应的执行计划记录的filtered值,它决定了被驱动表要执行的次数(即rows * filtered)
9、Extra (重点)
顾名思义,Extra列是用来说明一些额外的信息的,包含不适合在其他列中显示但十分重要的额外信息。我们可以通过这些额外信息来更准确的理解MySQL到底如何执行给定的查询语句。MySQL提供的额外信息有好几十个,这里就不一个一个介绍了,只挑比较重要的额外信息介绍给大家。
-
No tables use
当查询语句的没有FROM子句将会提示该额外信息
EXPLAIN SELECT 1
-
Impossible WHERE
查询语句的WHERE子句永远为FALSE时将会提示该额外信息
EXPLAIN SELECT * FROM S1 WHERE 1 != 1;
-
Using index
当我们的查询列表以及搜索条件中只包含属于某个索引的列,也就是在可以使用覆盖索引的情况下,在Extra列将会提示该额外信息。比方说下边这个查询中只需要用到index_key1而不需要回表操作
EXPLAIN SELECT key1 FROM S1 WHERE key1 = 'a'
-
Not exists
当我们使用左(外)连接时,如果WHERE子句中包含要求被驱动表的某个列等于NULL值的搜索条件,而且那个列又是不允许存储NULL值的,那么在该表的执行计划的Extra列就会提示NOT exists 额外信息。
EXPLAIN SELECT * FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.id IS NULL;
-
Using union(idx_key1,idx_key2)
索引合并查询会提示该额外信息
EXPLAIN SELECT * FROM s1 where s1.key1 = 'a' or s1.key2 = 'b'
-
Zero limit
当我们的LIMIT子句参数为0时,表示压根不打算从表中读出任何记录,将会提示该额外信息。
EXPLAIN SELECT * FROM s1 limit 0
-
Using filesort
很多情况下排序操作无法使用到索引,只能在内存中(记录较少的时候)或者磁盘中(记录较多的时候)进行排序,MySQL把这种在内存中或者磁盘上进行排序的方式统称为文件排序(filesort)
如果某个查询需要使用文件排序的方式进行查询,就会在执行计划中显示Using filesort
EXPLAIN SELECT * FROM s1 order by common_field limit 10