我们知道,当一条sql查询语句执行时,会通过服务层中的优化器生成“查询执行计划”。而使用explain关键字可以查询到执行的SQL查询语句,从而知道MySQL是如何处理SQL的,即SQL的执行计划。因此根据执行计划我们可以选择更好的索引和写出更优化的查询语句,分析我们的查询语句或是表结构的性能瓶颈。
一、 执行计划的概念
EXPLAIN SELECT * FROM t1 where t1.id > 1000 order by t1.nid desc
以上语句执行结果
首先先解释一下以上执行计划中各列的含义:
列 | JSON名称 | 意义 |
---|---|---|
id | select_id | SELECT标识符,查询中SELECT的序列号 |
select_type | None | 每一步的查询类型 |
table | table_name | 加载的表的名称 |
partitions | partitions | 查询将匹配记录的分区 |
type | access_type | 表的连接方式 |
possible_keys | possible_keys | 显示可能用到的索引 |
key | key | MySQL实际决定使用的索引 |
key_len | key_length | MySQL决定使用的索引的长度 |
ref | ref | 显示索引的哪些列被引用了 |
rows | rows | 所扫描的数据行数;不是所需数据的行数 |
filtered | filtered | 按表条件筛选的表行的估计百分比 |
Extra | None | 包含有关MySQL如何解析查询的其他信息 |
-
id
这个id和咱们平时表结构设计的主键ID不太一样,这里的id代表了每一条SQL语句执行计划中表加载的顺序,它的数值大小的意义为:- 当id大小相同时,从上到下依次执行;
- 当id不同时,id越大的越先执行。
-
select_type
1. SIMPLE:简单SELECT(不使用UNION或子查询);
EXPLAIN SELECT * FROM USER
SIMPLE
2. PRIMARY:如果查询语句中包含子查询或者UNION操作,指最外层的SELECT;
EXPLAIN SELECT * FROM USER_ROLE ur WHERE ur.USER_ID = (SELECT ID FROM USER WHERE ID = 1)
PRIMARY
3. UNION:UNION中的第二个或后面的SELECT语句;
EXPLAIN SELECT * FROM USER_ROLE T1 WHERE T1.USER_ID=1 UNION SELECT * FROM USER_ROLE T2 WHERE T2.USER_ID=2
UNION
4. UNION RESULT:UNION 的结果;
见UNION图
5. SUBQUERY:子查询中的第一个SELECT;
见PRIMARY图
6. DERIVED:导出表的SELECT(FROM子句的子查询)。
EXPLAIN SELECT tm.MENU_NAME,rm.ROLE_ID FROM (SELECT * FROM MENU WHERE ID >3 ) tm ,ROLE_MENU rm WHERE tm.ID=rm.MENU_ID AND rm.ROLE_ID=1
DERIVED
table
table代表对应步骤加载的是哪张表,也可能中间会出现一些临时表,比如subquery2、derived2等这种,最后的数字代表产生该表对应步骤的id
<union M.N>:该行指的是id值为M和N的行的结果;
<derived N>:该行指的是id值为N的行的派生表的结果(派生表可能来自FROM子句中的子查询);
<subquery N>:该行指的是id值为N的行的具体化子查询的结果-
type
代表访问类型,MySQL内部将其分为多类型,常用的类型从好到差的顺序展示如下:system >> const >> eq_ref >> ref >> fulltext >> ref_or_null >> index_merge >> unique_subquery >> index_subquery >> range >> index >> ALL
下面介绍在实际开发过程中,常见的几种类型:
1. const:表示通过索引一次就找到数据,用于比较primary key或者unique索引,很快就能找到对应的数据;
2. eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常用于主键或唯一索引扫描;
3. ref:非唯一索引扫描,返回匹配的所有行;
4. index_merge:经常出现在使用一张表中的多个索引时,mysql会将多个索引合并在一起;
5. range:使用一个索引检索指定范围的行,一般在where语句中会出现between、<、>、in等范围查询;
6. index:index连接类型与ALL相同,只是遍历索引树;
7. ALL:全表扫描,找到匹配行。与index比较,ALL需要扫描磁盘数据,index值需要遍历索引树。
possible_keys
显示可能被用到的索引,但在实际查询中不一定能用到; 查询涉及到字段,如果存在索引,会被列出,但如果使用的是覆盖索引,只会在key中列出。key
显示MySQL实际决定使用的键(索引)。如果没有选择索引,键是NULL。key_lens
显示MySQL决定使用的键长度(字节数),根据这个值可以判断索引的使用情况,特别是在组合索引的时候,判断该索引有多少部分被使用到,非常重要,key_len是根据表定义计算而得。ref
ref列显示为了从表中选择行,哪些列或者常量需要与key列中指定的索引进行比较。rows
显示MySQL认为它执行查询时必须检查的行数。多行之间的数据相乘可以估算要处理的行数。filtered
显示了通过条件过滤出的行数的百分比估计值。最大值为100,这意味这不会对行进行过滤,值一般越小越好。Extra
该列包含MySQL解决查询的详细信息,对于优化SQL具有重要参考意义,下面是一些常见的信息:
1. Using where:WHERE 子句用于限制哪些行与下一个表匹配或发送到客户端;
2. Using index:仅使用索引树中的信息从表中检索列信息,而不必执行额外的搜索,查询效率比较好;
3. Distinct:MySQL发现第1个匹配行后,停止为当前的行组合搜索更多的行;
4. Using temporary:为了解决查询,MySQL需要创建一个临时表来容纳结果;
5. Using filesort:MySQL需要额外的一次传递,以找出如何按排序顺序检索行;
6. Not exists:MySQL能够对查询进行LEFT JOIN优化,发现1个匹配LEFT JOIN标准的行后,不再为前面的的行组合在该表内检查更多的行。
二、执行计划实践---实例分析
-
key与key_len
key_len的作用是看索引哪些字段被用到,具体来说是看联合索引。因为索引只有一个字段的话,你直接看key就行了。联合索引有很多字段,哪些用到索引,哪些没有,通过key_len可以计算得到。
下面讲些常用的字段的占用空间计算规则,
一般来讲,key_len的值=使用了索引的字段的所占空间(字节)之和,这个单位是字节。但是仅仅知道这个规则计算的时候经常是错误的,我们还要考虑字段类型,mysql版本,是否允许为空,是否可变长度,还有字符编码集。
int 占用4个字节
char 一个字节
varchar 3个字节
datatime 5~8个字节
当知道字段运行为空的时候,底层需要一个字节标注这个字段的空约束。而不允许为空的时候不需要标注,因为不允许为空是默认的。当字符串是可变长度类型的时候,varchar就是这个类型。那么除了空约束以为,还有可变长度这个约束占用2个字节。再比如,datatime在mysql5.6是5个字节,可是在5.5是8个字节。
误区:
未使用全部索引
explain执行计划
上述图片可以看到,key_len的值为9(即hotelID(4)+dateTime(5)),没有使用到全部联合索引,以下是改良后的sql语句:
改良后sql语句
explain执行计划
此时key_len的值为14(即hotelID(4)+dateTime(5)+dateTime(5)),使用到了key中所有索引。
- type与key
优化前:
sql语句
执行计划
很显然,从explain执行计划中可以看到,该sql语句使用了两个索引,但是从我们自己的优化目标中,只需要使用IDX_DataChange_CreateTime这一个索引就够了,以下是我们通过一些小手段影响优化器得到的优化方案:
优化方案一
执行计划
优化方案二