zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

转 Mysql查看执行计划

mysql执行 查看 计划
2023-09-11 14:18:40 时间

############ sample 1

 

感谢张路路

 

##几个关键点
关键点1.
对于单表查询来说,这个filtered列的值没什么意义,我们更关注在连接查询中驱动表对应的执行计划记录的filtered值。
DESC SELECT * FROM t1 INNER JOIN t2 ON t1.key1 = t2.key1 WHERE t1.common_field = 'a';
图片
从执行计划中可以看出来,查询优化器打算把t1当作驱动表,t2当作被驱动表。我们可以看到驱动表t1表的执行计划的rows列为9991, filtered列为10.00,
这意味着驱动表t1的扇出值就是9991 × 10.00% = 999.1,这说明还要对被驱动表执行大约999次查询。


如果查询中需要使用filesort的方式进行排序的记录非常多,那么这个过程是很耗费性能的,我们最好想办法将使用文件排序的执行方式改为使用索引进行排序。

关键点2.普通的查询
1 SIMPLE PARTITIONS ALL PARTITIONS_N49 36665 100.00 Using where; Using filesort
1 SIMPLE TBLS eq_ref PRIMARY,TBLS_N49 PRIMARY 8 metastore.PARTITIONS.TBL_ID 1 100.00 Using where
1 SIMPLE DBS eq_ref PRIMARY PRIMARY 8 metastore.TBLS.DB_ID 1 100.00 Using where

 

关键点3. FORMAT=JSON必须在mysql 命令行下,不能在连接工具里查询

use db:
DESC FORMAT=JSON SELECT * FROM t1 INNER JOIN t2 ON t1.key1 = t2.key2 WHERE t1.common_field = 'a'\G

关注这3项目:
rows_produced_per_join 检查的行数
"attached_condition , 该连接触发条件
prefix_cost 连接的 cost总成本

详细的查询

| {
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "95546.60"
},
"ordering_operation": {
"using_filesort": true,
"cost_info": {
"sort_cost": "137337.45"
},
"nested_loop": [
{
"table": {
"table_name": "PARTITIONS",
"access_type": "ALL",
"possible_keys": [
"PARTITIONS_N49"
],
"rows_examined_per_scan": 36666,
"rows_produced_per_join": 36666,
"filtered": "100.00",
"cost_info": {
"read_cost": "215.00",
"eval_cost": "7333.20",
"prefix_cost": "7548.20",
"data_read_per_join": "28M"
},
"used_columns": [
"PART_ID",
"CREATE_TIME",
"PART_NAME",
"TBL_ID"
],
"attached_condition": "(`metastore`.`PARTITIONS`.`TBL_ID` is not null)"
}
},
{
"table": {
"table_name": "TBLS",
"access_type": "eq_ref",
"possible_keys": [
"PRIMARY",
"TBLS_N49"
],
"key": "PRIMARY",
"used_key_parts": [
"TBL_ID"
],
"key_length": "8",
"ref": [
"metastore.PARTITIONS.TBL_ID"
],
"rows_examined_per_scan": 1,
"rows_produced_per_join": 36666,
"filtered": "100.00",
"cost_info": {
"read_cost": "36666.00",
"eval_cost": "7333.20",
"prefix_cost": "51547.40",
"data_read_per_join": "43M"
},
"used_columns": [
"TBL_ID",
"DB_ID",
"TBL_NAME"
],
"attached_condition": "((lower(`metastore`.`TBLS`.`TBL_NAME`) = 'fct_yw_pro_01_da_od000_v1') and (`metastore`.`TBLS`.`DB_ID` is not null))"
}
},
{
"table": {
"table_name": "DBS",
"access_type": "eq_ref",
"possible_keys": [
"PRIMARY"
],
"key": "PRIMARY",
"used_key_parts": [
"DB_ID"
],
"key_length": "8",
"ref": [
"metastore.TBLS.DB_ID"
],
"rows_examined_per_scan": 1,
"rows_produced_per_join": 36666,
"filtered": "100.00",
"cost_info": {
"read_cost": "36666.00",
"eval_cost": "7333.20",
"prefix_cost": "95546.60",
"data_read_per_join": "289M"
},
"used_columns": [
"DB_ID",
"NAME"
],
"attached_condition": "(lower(`metastore`.`DBS`.`NAME`) = 'idm')"
}
}
]
}
}
} |

 

关键点4.MySQL在执行计划中输出key_len列主要是为了让我们区分某个使用联合索引的查询具体用了几个索引列,而不是为了准确的说明针对某个具体存储引擎存储变长字段的实际长度占用的空间到底是占用1个字节还是2个字节。

 

关键点5.主要关注prefix_cost的值代表的是整个连接查询预计的成本,也就是单次查询t1表和多次查询t2表后的成本的和

 

 

 

##文章1

MySQL之执行计划详解(一)

原创 张路路 GrowthDBA 2021-12-30 19:50

收录于合集

#MySQL63个

#SQL25个

经过之前文章的知识铺垫,终于迎来执行计划的相关知识(千呼万唤始出来)。前面我们学习分析了单表查询成本的计算过程MySQL之单表查询成本、多表连接查询成本的计算过程MySQL之连接查询成本,这些过程其实都是MySQL优化器帮我做,我们无需特别关注,我们只需要知道优化器帮我们从N种执行方式中选择出成本最低方式去执行SQL就好。

一条SQL语句经过查询优化器的各种基于成本和规则的优化会后生成一个执行成本最低的方式,这个方式就是所谓的执行计划,执行计划展示了接下来具体执行查询的方式,比如单表查询,使用哪个索引效率更高、连接查询哪张表作为驱动表更合适、每张表采用什么访问方法具体执行查询等等。MySQL提供了EXPLAIN语句来帮助我们查看某个SQL语句的具体执行计划。今天我们就来学习一下查询优化的百科全书——EXPLAIN执行计划。

 

 

执行计划输出各列的含义详解

 

 



如果我们想查看某个查询的执行计划,可以在具体的查询语句前面加一个EXPLAIN或DESC或DESCRIBE均可,就像下面这样。

 

输出的内容就是执行计划。除了将上述三个关键字任意一个加在SELECT语句前,DELETE、INSERT、UPDATE语句前也可以添加。EXPLAIN语句输出的各个列代表的含义如下:

列名

描述

id

执行编号,标识SELECT所属的行,在一个大的查询语句中每个SELECT关键字都对应一个唯一的id,只有唯一的SELECT,每行都将显示1,否则,内层的SELECT语句一般会顺序编号,对应于其在原始语句中的位置。

select_type

SELECT关键字对应的那个查询的类型

table

访问引用的表名

partitions

匹配的分区信息

type

针对单表的访问方法

possible_keys

可能用到的索引

key

实际使用到的索引

key_len

实际使用的索引长度(单位:字节,B)

ref

当使用索引列等值查询时,与索引列进行等值匹配的对象信息

rows

预估需要读取的记录条数

filtered

某个表经过搜索条件过滤后剩余记录条数的百分比

Extra

额外的信息

为了方便说明,我们还是准备两张之前用过的表:

CREATE TABLE `table_query_cost` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `key1` VARCHAR(100),
    `key2` INT(11) ,
    `key3` VARCHAR(100),
    `key_part1` VARCHAR(100),
    `key_part2` VARCHAR(100),
    `key_part3` VARCHAR(100),
    `common_field` VARCHAR(100),
    PRIMARY KEY (`id`),
    KEY idx_key1 (`key1`),
    UNIQUE KEY uq_key2 (`key2`),
    KEY idx_key3 (`key3`),
    KEY idx_key_part(`key_part1`, `key_part2`, `key_part3`)
) Engine=InnoDB;

 

创建2张和table_query_cost表结构一样的表t1、t2,并且除id列之外都插入随机值共10000行记录。注意我们不按照EXPLAIN输出列的顺序来介绍各列含义。

table

不管我们的查询语句多复杂,包含多少张表进行关联查询,最后都会下钻到对每个表进行单表访问。MySQL规定EXPLAIN语句输出的每条记录都对应着某个单表的访问方法,该条记录的table列代表着该表的表名。

DESC SELECT * FROM t1;

 

单表查询,DESC只输出一条记录,table列是t1,表示这条记录是说明对t1表单表访问方法的。

DESC SELECT * FROM t1 INNER JOIN t2;

 

执行计划有2条记录,table列分别是t1、t2,分别记录对t1和t2的访问方法是什么。

id

查询语句中每出现一个SELECT关键字,MySQL就会为它分配一个唯一的id值。这个id值就是EXPLAIN语句的第一个列。

DESC SELECT * FROM t1 WHERE key1 = 'a';

 

对于连接查询来说,一个SELECT关键字后边的FROM子句中可以跟随多个表,所以在连接查询的执行计划中,每个表都会对应一条记录,但是这些记录的id值都是相同的。

DESC SELECT * FROM t1 INNER JOIN t2;

 

可以看到,上述连接查询中参与连接的t1和t2表分别对应一条记录,但是这两条记录对应的id值都是1。在连接查询的执行计划中,每个表都会对应一条记录,这些记录的id列的值是相同的,出现在前边的表表示驱动表,出现在后边的表表示被驱动表。所以从上边的EXPLAIN输出中我们可以看出,查询优化器准备让t1表作为驱动表,让t2表作为被驱动表来执行查询。对于包含子查询的查询语句来说,就可能涉及多个SELECT关键字,所以在包含子查询的查询语句的执行计划中,每个SELECT关键字都会对应一个唯一的id值。

DESC SELECT * FROM t1 WHERE key1 IN (SELECT key1 FROM t2) OR key3 = 'a';

 

从输出结果中我们可以看到,t1表在外层查询中,外层查询有一个独立的SELECT关键字,所以第一条记录的id值就是1,t2表在子查询中,子查询有一个独立的SELECT关键字,所以第二条记录的id值就是2。这里需要特别注意,查询优化器可能对涉及子查询的查询语句进行重写,从而转换为连接查询。所以如果我们想知道查询优化器对某个包含子查询的语句是否进行了重写,直接查看执行计划就可以,出现2次SELECT关键字的子查询,如果执行计划中id列都是1,那就表明查询优化器将子查询转换为了连接查询。对于包含UNION子句的查询语句来说,每个SELECT关键字对应一个id值也是没错的,但有一个情况需要注意:

DESC SELECT * FROM t1 UNION SELECT * FROM t2;

 

输出的结果可以看出,第三行内容的id值为NULL,table列也不是表名。UNION子句会把多个查询的结果集合并起来并对结果集中的记录进行去重,MySQL使用的是内部的临时表进行去重,UNION子句把id为1的查询和id为2的查询的结果集合并起来并去重,所以在内部创建了一个名为<union1, 2>的临时表,id为NULL表明这个临时表是为了合并两个查询的结果集而创建的。跟UNION对比起来,UNION ALL就不需要为最终的结果集进行去重,它只是单纯的把多个查询的结果集中的记录合并成一个并返回给用户,所以也就不需要使用临时表。

DESC SELECT * FROM t1 UNION ALL SELECT * FROM t2;

 

所以在包含UNION ALL子句的查询的执行计划中,就没有那个id为NULL的记录。

select_type

一条大的查询语句里边可以包含若干个SELECT关键字,每个SELECT关键字代表着一个小的查询语句,而每个SELECT关键字的FROM子句中都可以包含若干张表(这些表用来做连接查询),每一张表都对应着执行计划输出中的一条记录,对于在同一个SELECT关键字中的表来说,它们的id值是相同的。MySQL为每一个SELECT关键字代表的小查询都定义了一个称之为select_type的属性,意思是我们只要知道了某个小查询的select_type属性,就知道了这个小查询在整个大查询中扮演了的角色。select_type的取值如下:

名称

描述

SIMPLE

Simple SELECT (not using UNION or subqueries)

PRIMARY

Outermost SELECT

UNION

Second or later SELECT statement in a UNION

UNION RESULT

Result of a UNION

SUBQUERY

First SELECT in subquery

DEPENDENT SUBQUERY

First SELECT in subquery, dependent on outer query

DEPENDENT UNION

Second or later SELECT statement in a UNION, dependent on outer query

DERIVED

Derived table

MATERIALIZED

Materialized subquery

UNCACHEABLE SUBQUERY

A subquery for which the result cannot be cached and must be re-evaluated for each row of the outer query

UNCACHEABLE UNION

The second or later select in a UNION that belongs to an uncacheable subquery (see UNCACHEABLE SUBQUERY)

下面来一一详细介绍一下:

SIMPLE

查询语句中不包含UNION或者子查询的查询都算作是SIMPLE类型。

DESC SELECT * FROM t1;

 

连接查询也是SIMPLE类型。

DESC SELECT * FROM t1 INNER JOIN t2;

 

PRIMARY

对于包含UNION、UNION ALL或者子查询的大查询来说,它是由几个小查询组成的,其中最左边的那个查询的select_type值就是PRIMARY。

DESC SELECT * FROM t1 UNION SELECT * FROM t2;

 

由结果可得,最左边的小查询SELECT * FROM t1对应的是执行计划中的第一条记录,它的select_type值就是PRIMARY。

UNION

包含UNION或者UNION ALL的大查询来说,它是由几个小查询组成的,其中除了最左边的那个小查询以外,其余的小查询的select_type值就是UNION,同上例。

UNION RESULT

MySQL选择使用临时表来完成UNION查询的去重工作,针对该临时表的查询的select_type就是UNION RESULT,同上例。

SUBQUERY

如果包含子查询的查询语句不能够转为对应的semi-join的形式,并且该子查询是不相关子查询,并且查询优化器决定采用将该子查询物化的方案来执行该子查询时,该子查询的第一个SELECT关键字代表的那个查询的select_type就是SUBQUERY。

DESC SELECT * FROM t1 WHERE key1 IN (SELECT key1 FROM t2) OR key3 = 'a';

 

可以看到,外层查询的select_type就是PRIMARY,子查询的select_type就是SUBQUERY。需要大家注意的是,由于select_type为SUBQUERY的子查询会被物化,所以只需要执行一遍

  • DEPENDENT SUBQUERY

如果包含子查询的查询语句不能够转为对应的semi-join的形式,并且该子查询是相关子查询,则该子查询的第一个SELECT关键字代表的那个查询的select_type就是DEPENDENT SUBQUERY。

DESC SELECT * FROM t1 WHERE key1 IN (SELECT key1 FROM t2 WHERE t1.key2 = t2.key2) OR key3 = 'a';

 

小提示

select_type为DEPENDENT SUBQUERY的查询可能会被执行多次

DEPENDENT UNION

包含UNION或者UNION ALL的大查询中,如果各个小查询都依赖于外层查询的话,那除了最左边的那个小查询之外,其余的小查询的select_type的值就是DEPENDENT UNION。

DESC SELECT * FROM t1 WHERE key1 IN (SELECT key1 FROM t2 WHERE key1 = 'a' UNION SELECT key1 FROM t1 WHERE key1 = 'b');

 

这个大查询里包含了一个子查询,子查询里又是由UNION连起来的两个小查询。从执行计划中可以看出来,SELECT key1 FROM t2 WHERE key1 = 'a'这个小查询由于是子查询中第一个查询,所以它的select_type是DEPENDENT SUBQUERY,而SELECT key1 FROM t1 WHERE key1 = 'b'这个查询的select_type就是DEPENDENT UNION。

DERIVED

采用物化的方式执行的包含派生表的查询,该派生表对应的子查询的select_type就是DERIVED。

DESC SELECT * FROM (SELECT key1, count(*) as c FROM t1 GROUP BY key1) AS derived_t1 where c > 1;

 

从执行计划中可以看出,id为2的记录就代表子查询的执行方式,它的select_type是DERIVED说明该子查询是以物化的方式执行的。(回顾一下物化的概念MySQL之SQL优化相关术语必知Materialize:物化,将子查询结果集中的记录保存到临时表的过程称之为物化。)id为1的记录代表外层查询,它的table列显示的是<derived2>,表示该查询是针对将派生表物化之后的表进行查询的。

MATERIALIZED

当查询优化器在执行包含子查询的语句时,选择将子查询物化之后与外层查询进行连接查询时,该子查询对应的select_type属性就是MATERIALIZED。

DESC SELECT * FROM t1 WHERE key1 IN (SELECT key1 FROM t2);

 

执行计划的第三条记录的id值为2,说明该条记录对应的是一个单表查询,从它的select_type值为MATERIALIZED可以看出,查询优化器是要把子查询先转换成物化表。然后看执行计划的前两条记录的id值都为1,说明这两条记录对应的表进行连接查询,需要注意的是第二条记录的table列的值是<subquery2>,说明该表其实就是id为2对应的子查询执行之后产生的物化表,然后将t1和该物化表进行连接查询。

UNCACHEABLE SUBQUERY、UNCACHEABLE UNION

不常用,很少见。

 

partitions

分区信息,很少见,一般情况下该列的值都是NULL,不再赘述。

 

type

执行计划的一条记录就代表着MySQL对某个表的执行查询时的访问方法,其中的type列就表明了这个访问方法。

DESC SELECT * FROM t1 WHERE key1 = 'a';

 

可以看到type列的值是ref,表明MySQL即将使用ref访问方法来执行对t1表的查询。但是我们之前只唠叨过对使用InnoDB存储引擎的表进行单表访问的一些访问方法,完整的访问方法如下:system,const,eq_ref,ref,fulltext,ref_or_null,index_merge,unique_subquery,index_subquery,range,index,ALL

system:当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的,比如MyISAM、Memory,那么对该表的访问方法就是system。

CREATE TABLE t(i int) Engine=MyISAM;
INSERT INTO t VALUES(1);
DESC SELECT * FROM t;

 

 

cost:根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是const。

DESC SELECT * FROM t1 WHERE id = 666;

 

eq_ref:在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的(如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较),则对该被驱动表的访问方法就是eq_ref。

DESC SELECT * FROM t1 INNER JOIN t2 ON t1.id = t2.id;

 

MySQL打算将t1表作为驱动表,t2表作为被驱动表。被驱动表的访问方法是eq_ref,说明在访问t2表的时候可以通过主键的等值匹配来进行访问。

ref:当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就可能是ref。

fulltext:全文索引,暂不介绍。

  • ref_or_null:对普通二级索引进行等值匹配查询,该索引列的值也可以是NULL值时,那么对该表的访问方法就可能是ref_or_null。

DESC SELECT * FROM t1 WHERE key1 = 'a' OR key1 IS NULL;

 

index_merge:一般情况下对于某个表的查询只能使用到一个索引,但我们唠叨单表访问方法时特意强调了在某些场景下可以使用Intersection、Union、Sort-Union这三种索引合并的方式来执行查询。

DESC SELECT * FROM t1 WHERE key1 = 'a' OR key3 = 'a';

 

执行计划的type列的值是index_merge就可以看出,MySQL打算使用索引合并的方式来执行对t1表的查询。

unique_subquery:类似于两表连接中被驱动表的eq_ref访问方法,unique_subquery是针对在一些包含IN子查询的查询语句中,如果查询优化器决定将IN子查询转换为EXISTS子查询,而且子查询可以使用到主键进行等值匹配的话,那么该子查询执行计划的type列的值就是unique_subquery。

DESC SELECT * FROM t1 WHERE key2 IN (SELECT id FROM t2 where t1.key1 = t2.key1) OR key3 = 'a';

 

执行计划的第二条记录的type值就是unique_subquery,说明在执行子查询时会使用到id列的索引。

index_subqueryindex_subquery与unique_subquery类似,只不过访问子查询中的表时使用的是普通的索引。

DESC SELECT * FROM t1 WHERE common_field IN (SELECT key3 FROM t2 where t1.key1 = t2.key1) OR key3 = 'a';

 

range:如果使用索引获取某些范围区间的记录,那么就可能使用到range访问方法。

DESC SELECT * FROM t1 WHERE key1 IN ('a', 'b', 'c');

 

或:

DESC SELECT * FROM t1 WHERE key1 > 'a' AND key1 < 'b';

 

index:当我们可以使用索引覆盖,但需要扫描全部的索引记录时,该表的访问方法就是index。

DESC SELECT key_part2 FROM t1 WHERE key_part3 = 'a';

 

上述查询中的搜索列表中只有key_part2一个列,而且搜索条件中也只有key_part3一个列,这两个列又恰好包含在idx_key_part这个索引中,可是搜索条件key_part3不能直接使用该索引进行ref或者range方式的访问,只能扫描整个idx_key_part索引的记录,所以查询计划的type列的值就是index。小提示对于使用InnoDB存储引擎的表来说,二级索引的记录只包含索引列和主键列的值,而聚簇索引中包含用户定义的全部列以及一些隐藏列,所以扫描二级索引的代价比直接全表扫描,也就是扫描聚簇索引的代价更低一些

ALL:全表扫描。

DESC SELECT * FROM t1;

 

一般来说,这些访问方法按照我们介绍它们的顺序性能依次变差。其中除了All这个访问方法外,其余的访问方法都能用到索引,除了index_merge访问方法外,其余的访问方法都最多只能用到一个索引。

 

possible_keys和key

输出的执行计划中,possible_keys列表示在某个查询语句中,对某个表执行单表查询时可能用到的索引有哪些,key列表示实际用到的索引有哪些。

DESC SELECT * FROM t1 WHERE key1 > 'z' AND key3 = 'a';

 

上述执行计划的possible_keys列的值是idx_key1,idx_key3,表示该查询可能使用到idx_key1,idx_key3两个索引,然后key列的值是idx_key3,表示经过查询优化器计算使用不同索引的成本后,最后决定使用idx_key3来执行查询成本较低。

有一点比较特别,就是在使用index访问方法来查询某个表时,possible_keys列是空的,而key列展示的是实际使用到的索引。

DESC SELECT key_part2 FROM t1 WHERE key_part3 = 'a';

 

小提示possible_keys列中的值并不是越多越好,可能使用的索引越多,查询优化器计算查询成本时就得花费更长时间,所以如果可以的话,尽量删除那些用不到的索引

key_len

key_len列表示当优化器决定使用某个索引执行查询时,该索引记录的最大长度,它是由这三个部分构成的:

1、对于使用固定长度类型的索引列来说,它实际占用的存储空间的最大长度就是该固定值,对于指定字符集的变长类型的索引列来说,比如某个索引列的类型是VARCHAR(100),使用的字符集是utf8mb4,那么该列实际占用的最大存储空间就是100 × 4 = 400个字节。

2、如果该索引列可以存储NULL值,则key_len比不可以存储NULL值时多1个字节。

3、对于变长字段来说,都会有2个字节的空间来存储该变长列的实际长度。

DESC SELECT * FROM t1 WHERE id = 66;

 

id列的类型是INT,并且不可以存储NULL值,所以在使用该列的索引时key_len大小就是4。当索引列可以存储NULL值时。

DESC SELECT * FROM t1 WHERE key2 = 66;

 

看到key_len列就变成了5,比使用id列的索引时多了1。对于可变长度的索引列来说。

DESC SELECT * FROM t1 WHERE key1 = 'a';

 

key1列的类型是VARCHAR(100),所以该列实际最多占用的存储空间就是400字节,又因为该列允许存储NULL值,所以key_len需要加1,又因为该列是可变长度列,所以key_len需要加2,所以最后ken_len的值就是403

之前文章MySQL之InnoDB记录结构学习过InnoDB存储变长字段的实际长度不是可能占用1个字节或者2个字节,但是在计算key_len的时候都是以2字节计算?需要强调的一点是,执行计划的生成是在MySQL server层中的功能,并不是针对具体某个存储引擎的功能,MySQL在执行计划中输出key_len列主要是为了让我们区分某个使用联合索引的查询具体用了几个索引列,而不是为了准确的说明针对某个具体存储引擎存储变长字段的实际长度占用的空间到底是占用1个字节还是2个字节。

使用到联合索引idx_key_part的查询:

DESC SELECT * FROM t1 WHERE key_part1 = 'a';

 

执行计划的key_len列中看到值是403,这意味着MySQL在执行上述查询中只能用到idx_key_part索引的一个索引列。

DESC SELECT * FROM t1 WHERE key_part1 = 'a' AND key_part2 = 'b';

 

执行计划的ken_len列的值是806,说明执行这个查询的时候可以用到联合索引idx_key_part的两个索引列。

ref

当使用索引列等值匹配的条件去执行查询时,也就是在访问方法是const、eq_ref、ref、ref_or_null、unique_subquery、index_subquery其中之一种,ref列展示的就是与索引列作等值匹配的对象是什么

比如对象只是一个常数:

DESC SELECT * FROM t1 WHERE key1 = 'a';

 

可以看到ref列的值是const,表明在使用idx_key1索引执行查询时,与key1列作等值匹配的对象是一个常数。

DESC SELECT * FROM t1 INNER JOIN t2 ON t1.id = t2.id;

 

看到对被驱动表t2的访问方法是eq_ref,而对应的ref列的值是test_cost.t1.id,这说明在对被驱动表进行访问时会用到PRIMARY索引,也就是聚簇索引与一个列进行等值匹配的条件,于t2表的id作等值匹配的对象就是test_cost.t1.id列(注意这里把数据库名也写出来了)。
有的时候与索引列进行等值匹配的对象是一个函数。

DESC SELECT * FROM t1 INNER JOIN t2 ON t2.key1 = UPPER(t1.key1);

 

执行计划的第二条记录,可以看到对t2表采用ref访问方法执行查询,然后在查询计划的ref列里输出的是func,说明与t2表的key1列进行等值匹配的对象是一个函数。

rows

查询优化器决定使用全表扫描的方式对某个表执行查询时,执行计划的rows列就代表预计需要扫描的行数,如果使用索引来执行查询时,执行计划的rows列就代表预计扫描的索引记录行数。

DESC SELECT * FROM t1 WHERE key1 > 'z';

 

看到执行计划的rows列的值是1,这意味着查询优化器在经过分析使用idx_key1进行查询的成本之后,觉得满足key1 > 'z'这个条件的记录只有1条。

filtered

上篇文章MySQL之连接查询成本提出过一个condition filtering的概念,就是MySQL在计算驱动表扇出时采用的一个策略:

如果使用的是全表扫描的方式执行的单表查询,那么计算驱动表扇出时需要估计出满足搜索条件的记录到底有多少条。

如果使用的是索引执行的单表扫描,那么计算驱动表扇出的时候需要估计出满足除使用到对应索引的搜索条件外的其他搜索条件的记录有多少条。

DESC SELECT * FROM t1 WHERE key1 > 'z' AND common_field = 'a';

 

从执行计划的key列中可以看出来,该查询使用idx_key1索引来执行查询,从rows列可以看出满足key1 > 'z'的记录有1条。执行计划的filtered列就代表查询优化器预测在这1条记录中,有多少条记录满足其余的搜索条件,也就是common_field = 'a'这个条件的百分比。此处filtered列的值是10.00,说明查询优化器预测在1条记录中有10.00%的记录满足common_field = 'a'这个条件。

对于单表查询来说,这个filtered列的值没什么意义,我们更关注在连接查询中驱动表对应的执行计划记录的filtered值

DESC SELECT * FROM t1 INNER JOIN t2 ON t1.key1 = t2.key1 WHERE t1.common_field = 'a';

 

从执行计划中可以看出来,查询优化器打算把t1当作驱动表,t2当作被驱动表。我们可以看到驱动表t1表的执行计划的rows列为9991 filtered列为10.00,这意味着驱动表t1的扇出值就是9991 × 10.00% = 999.1,这说明还要对被驱动表执行大约999次查询。

 

 

##文章2

MySQL之执行计划详解(二)

原创 张路路 GrowthDBA 2022-01-05 18:30

收录于合集

#MySQL63个

#SQL25个

这是2022年的第一篇文章,新年新征程,祝大家心想事成、健康暴富。接上文MySQL之执行计划详解(一),我们继续学习解读执行计划。执行计划的输出列中,就剩下Extra。今天就来学习一下Extra输出内容的详细含义。

MySQL官方文档中:https://dev.mysql.com/doc/refman/5.7/en/explain-output.html#explain_extraExtra提供的额外信息有很多,我们只学习工作中常用的额外信息。大家感兴趣的话可以去官网学习测试。

 

 

执行计划输出各列的含义详解

 

 



Extra

Extra列是用来说明一些额外信息,可以通过这些额外信息来更准确的理解MySQL到底将如何执行给定的查询语句。

No tables used

当查询语句没有FROM子句时会提示该额外信息。

DESC SELECT 1;

 

Impossible WHERE

WHERE子句永远为FALSE时将会提示该额外信息。

EXPLAIN SELECT * FROM t1 WHERE 1 != 1;

 

No matching min/max row

当查询列表处有MIN或者MAX聚集函数,但是并没有符合WHERE子句中的搜索条件的记录时,将会提示该额外信息。

EXPLAIN SELECT MIN(key1) FROM t1 WHERE key1 = 'abcdefg';

 

Using index

当查询列表以及搜索条件中只包含属于某个索引的列,也就是在可以使用索引覆盖的情况下,在Extra列将会提示该额外信息。

EXPLAIN SELECT key1 FROM t1 WHERE key1 = 'a';

 

Using index condition

有些搜索条件中虽然出现了索引列,但却不能使用到索引:

DESC SELECT * FROM t1 WHERE key1 > 'z' AND key1 LIKE '%a';

其中的key1 > 'z'可以使用到索引,但是key1 LIKE '%a'却无法使用到索引,在以前版本的MySQL中,是按照下边步骤来执行这个查询的:

1、先根据key1 > 'z'这个条件,从二级索引idx_key1中获取到对应的二级索引记录。

2、根据上一步骤得到的二级索引记录中的主键值进行回表,找到完整的用户记录再检测该记录是否符合key1 LIKE '%a'这个条件,将符合条件的记录加入到最后的结果集。

但是虽然key1 LIKE '%a'不能组成范围区间参与range访问方法的执行,但这个条件毕竟只涉及到了key1列,所以MySQL将上面的步骤改进了一下:

1、先根据key1 > 'z'这个条件,定位到二级索引idx_key1中对应的二级索引记录。

2、对于指定的二级索引记录,先不着急回表,而是先检测一下该记录是否满足key1 LIKE '%a'这个条件,如果这个条件不满足,则该二级索引记录没必要回表。

3、对于满足key1 LIKE '%a'这个条件的二级索引记录执行回表操作。

回表操作其实是一个随机IO,比较耗时,所以上述修改虽然只改进了一点点,但是可以省去好多回表操作的成本。MySQL把这个改进称之为索引条件下推(英文名:Index Condition Pushdown)

如果在查询语句的执行过程中将要使用索引条件下推这个特性,在Extra列中将会显示Using index condition。

DESC SELECT * FROM t1 WHERE key1 > 'z' AND key1 LIKE '%b';

 

Using where

当我们使用全表扫描来执行对某个表的查询,并且该语句的WHERE子句中有针对该表的搜索条件时,在Extra列中会提示上述额外信息。

DESC SELECT * FROM t1 WHERE common_field = 'a';

 

当使用索引访问来执行对某个表的查询,并且该语句的WHERE子句中有除了该索引包含的列之外的其他搜索条件时,在Extra列中也会提示上述额外信息。比如下边这个查询虽然使用idx_key1索引执行查询,但是搜索条件中除了包含key1的搜索条件key1 = 'a',还有包含common_field的搜索条件,所以Extra列会显示Using where的提示:

DESC SELECT * FROM t1 WHERE key1 = 'a' AND common_field = 'a';

 

Using join buffer (Block Nested Loop)

在连接查询执行过程中,当被驱动表不能有效的利用索引加快访问速度,MySQL一般会为其分配一块名叫join buffer的内存块来加快查询速度,也就是我们所讲的基于块的嵌套循环算法(这个只是大家应该很熟悉了,哈哈,这回就把知识串起来了)。

DESC SELECT * FROM t1 INNER JOIN t2 ON t1.common_field = t2.common_field;

 

t2表的执行计划的Extra列显示了两个提示:

Using join buffer (Block Nested Loop):这是因为对表t2的访问不能有效利用索引,只好退而求其次,使用join buffer来减少对t2表的访问次数,从而提高性能。

Using where:可以看到查询语句中有一个t1.common_field = t2.common_field条件,因为t1是驱动表,t2是被驱动表,所以在访问t2表时,t1.common_field的值已经确定下来了,所以实际上查询t2表的条件就是t2.common_field = 一个常数,所以提示了Using where额外信息。

  • Not exists

使用左(外)连接时,如果WHERE子句中包含要求被驱动表的某个列等于NULL值的搜索条件,而且那个列又是不允许存储NULL值的,那么在该表的执行计划的Extra列就会提示Not exists额外信息。

DESC SELECT * FROM t1 LEFT JOIN t2 ON t1.key1 = t2.key1 WHERE t2.id IS NULL;

 

上述查询中t1表是驱动表,t2表是被驱动表,t2.id列是不允许存储NULL值的,而WHERE子句中又包含t2.id IS NULL的搜索条件,这意味着必定是驱动表的记录在被驱动表中找不到匹配ON子句条件的记录才会把该驱动表的记录加入到最终的结果集,所以对于某条驱动表中的记录来说,如果能在被驱动表中找到1条符合ON子句条件的记录,那么该驱动表的记录就不会被加入到最终的结果集,也就是说我们没有必要到被驱动表中找到全部符合ON子句条件的记录,这样可以稍微节省一点性能。

Using intersect(...)、Using union(...)和Using sort_union(...)

如果执行计划的Extra列出现了Using intersect(...)提示,说明准备使用Intersect索引合并的方式执行查询,括号中的...表示需要进行索引合并的索引名称;如果出现了Using union(...)提示,说明准备使用Union索引合并的方式执行查询;出现了Using sort_union(...)提示,说明准备使用Sort-Union索引合并的方式执行查询。

DESC SELECT * FROM t1 WHERE key1 = '61' AND key3 = 'f05';

 

Extra列显示了Using intersect(idx_key3,idx_key1),表明MySQL即将使用idx_key3和idx_key1这两个索引进行Intersect索引合并的方式执行查询。

Zero limit

LIMIT子句的参数为0时,表示不打算从表中读出任何记录,将会提示该额外信息。

EXPLAIN SELECT * FROM t1 LIMIT 0;

 

Using filesort

有一些情况下对结果集中的记录进行排序是可以使用到索引的。

DESC SELECT * FROM t1 ORDER BY key1 LIMIT 10;

 

这个查询语句可以利用idx_key1索引直接取出key1列的10条记录,然后再进行回表操作就好了。但是很多情况下排序操作无法使用到索引,只能在内存中(记录较少的时候)或者磁盘中(记录较多的时候)进行排序,MySQL把这种在内存中或者磁盘上进行排序的方式统称为文件排序(英文名:filesort)。如果某个查询需要使用文件排序的方式执行查询,就会在执行计划的Extra列中显示Using filesort提示。

DESC SELECT * FROM t1 ORDER BY common_field LIMIT 10;

 

小提示如果查询中需要使用filesort的方式进行排序的记录非常多,那么这个过程是很耗费性能的,我们最好想办法将使用文件排序的执行方式改为使用索引进行排序

Using temporary

在许多查询的执行过程中,MySQL可能会借助临时表来完成一些功能,比如去重、排序之类的,比如我们在执行许多包含DISTINCT、GROUP BY、UNION等子句的查询过程中,如果不能有效利用索引来完成查询,MySQL很有可能寻求通过建立内部的临时表来执行查询。如果查询中使用到了内部的临时表,在执行计划的Extra列将会显示Using temporary提示。

DESC SELECT DISTINCT common_field FROM t1;

 

DESC SELECT common_field, COUNT(*) AS amount FROM t1 GROUP BY common_field;

 

上述执行计划的Extra列不仅仅包含Using temporary提示,还包含Using filesort提示,可是我们的查询语句中明明没有写ORDER BY子句呀?这是因为MySQL会在包含GROUP BY子句的查询中默认添加上ORDER BY子句,也就是说上述查询其实和下边这个查询等价:

DESC SELECT common_field, COUNT(*) AS amount FROM t1 GROUP BY common_field ORDER BY common_field;

 

如果我们并不想为包含GROUP BY子句的查询进行排序,需要我们显式的写上ORDER BY NULL。

DESC SELECT common_field, COUNT(*) AS amount FROM t1 GROUP BY common_field ORDER BY NULL;

 

这回执行计划中就没有Using filesort的提示了,也就意味着执行查询时可以省去对记录进行文件排序的成本了。

另外,执行计划中出现Using temporary并不是一个好的征兆,因为建立与维护临时表要付出很大成本的,所以我们最好能使用索引来替代掉使用临时表,比方说下边这个包含GROUP BY子句的查询就不需要使用临时表:

DESC SELECT key1, COUNT(*) AS amount FROM t1 GROUP BY key1;

 

Extra的Using index的提示里我们可以看出,上述查询只需要扫描idx_key1索引就可以搞定了,不再需要临时表了。

Start temporary, End temporary

当查询中包含子查询,查询优化器会优先尝试将IN子查询转换成semi-join,而semi-join又有好多种执行策略,当执行策略为DuplicateWeedout时,也就是通过建立临时表来实现为外层查询中的记录进行去重操作时,驱动表查询执行计划的Extra列将显示Start temporary提示,被驱动表查询执行计划的Extra列将显示End temporary提示。

LooseScan

在将IN子查询转为semi-join时,如果采用的是LooseScan执行策略,则在驱动表执行计划的Extra列就是显示LooseScan提示。

DESC SELECT * FROM t1 WHERE key3 IN (SELECT key1 FROM t2 WHERE key1 > 'z');

 

  • FirstMatch(table_name)

在将In子查询转为semi-join时,如果采用的是FirstMatch执行策略,则在被驱动表执行计划的Extra列就是显示FirstMatch(table_name)提示。

DESC SELECT * FROM t1 WHERE common_field IN (SELECT key1 FROM t2 where t1.key3 = t2.key3);

 

 

 

 

其他格式的执行计划

 

 



JSON格式的执行计划

EXPLAIN语句输出中缺少了一个衡量执行计划好坏的重要属性 —— 成本。不过MySQL贴心的为我们提供了一种查看某个执行计划花费的成本的方式:

EXPLAIN(DESC或DESCRIBE)和真正的查询语句中间加上FORMAT=JSON。这样我们就可以得到一个json格式的执行计划,里边儿包含该计划花费的成本。

mysql> DESC FORMAT=JSON SELECT * FROM t1 INNER JOIN t2 ON t1.key1 = t2.key2 WHERE t1.common_field = 'a'\G
*************************** 1. row ***************************
EXPLAIN: {
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "3294.12"
    },
    "nested_loop": [
      {
        "table": {
          "table_name": "t1",
          "access_type": "ALL",
          "possible_keys": [
            "idx_key1"
          ],
          "rows_examined_per_scan": 9991,
          "rows_produced_per_join": 999,
          "filtered": "10.00",
          "cost_info": {
            "read_cost": "1895.38",
            "eval_cost": "199.82",
            "prefix_cost": "2095.20",
            "data_read_per_join": "2M"
          },
          "used_columns": [
            "id",
            "key1",
            "key2",
            "key3",
            "key_part1",
            "key_part2",
            "key_part3",
            "common_field"
          ],
          "attached_condition": "((`test_cost`.`t1`.`common_field` = 'a') and (`test_cost`.`t1`.`key1` is not null))"
        }
      },
      {
        "table": {
          "table_name": "t2",
          "access_type": "ref",
          "possible_keys": [
            "uq_key2"
          ],
          "key": "uq_key2",
          "used_key_parts": [
            "key2"
          ],
          "key_length": "5",
          "ref": [
            "test_cost.t1.key1"
          ],
          "rows_examined_per_scan": 1,
          "rows_produced_per_join": 999,
          "filtered": "100.00",
          "index_condition": "(`test_cost`.`t1`.`key1` = `test_cost`.`t2`.`key2`)",
          "cost_info": {
            "read_cost": "999.10",
            "eval_cost": "199.82",
            "prefix_cost": "3294.12",
            "data_read_per_join": "2M"
          },
          "used_columns": [
            "id",
            "key1",
            "key2",
            "key3",
            "key_part1",
            "key_part2",
            "key_part3",
            "common_field"
          ]
        }
      }
    ]
  }
}
1 row in set, 2 warnings (0.01 sec)

输出的内容有些多,我们来一起看一下:

输出的内容

描述

 

整个查询语句只有1个SELECT关键字,该关键字对应的id号为1

 

整个查询的执行成本预计为3294.12

 

几个表之间采用嵌套循环连接算法执行,以下是参与嵌套循环连接算法的各个表的信息

 

t1表是驱动表
访问方法为ALL,意味着使用全表扫描访问
可能使用的索引:idx_key1

 

查询一次t1表大致需要扫描9991条记录
驱动表t1的扇出是999
condition filtering代表的百分比:10%

 

单次查询t1表总共的成本为2095.20
读取的数据量2M(2×1024×1024)

 

执行查询中涉及到的列

 

对t1表访问时针对单表查询的条件:"((`test_cost`.`t1`.`common_field` = 'a') and (`test_cost`.`t1`.`key1` is not null))"

 

t2表是被驱动表
访问方法为ref,意味着使用索引等值匹配的方式访问
可能使用的索引:uq_key2
使用到的索引列:key2
key_len:5
与key2列进行等值匹配的对象:test_cost.t1.key1

 

查询一次t2表大致需要扫描1条记录
被驱动表t2的扇出是999(由于后边没有多余的表进行连接,所以这个值也没啥用)
condition filtering代表的百分比:100%

 

t2表使用索引进行查询的搜索条件:"(`test_cost`.`t1`.`key1` = `test_cost`.`t2`.`key2`)"

 

单次查询t1、多次查询t2表总共的成本3294.12
读取的数据量2M(2×1024×1024)

 

执行查询中涉及到的列

上述的输出中,我们有一个部分没有说明。下面先来看一下t1表的"cost_info"部分:

"cost_info": {
  "read_cost": "1895.38",
  "eval_cost": "199.82",
  "prefix_cost": "2095.20",
  "data_read_per_join": "2M"
}

  • read_cost的组成

1、IO成本
2、检测rows × (1 - filter)条记录的CPU成本小提示rows和filter都是我们前边介绍执行计划的输出列,在JSON格式的执行计划中,rows相当于rows_examined_per_scan,filtered名称不变

  • eval_cost的组成

检测rows × filter条记录的成本

  • prefix_cost就是单独查询t1表的成本

prefix_cost = read_cost + eval_cost

  • data_read_per_join

表示在此次查询中需要读取的数据量

再来看下t2表的"cost_info"部分:

"cost_info": {
  "read_cost": "999.10",
  "eval_cost": "199.82",
  "prefix_cost": "3294.12",
  "data_read_per_join": "2M"
}

由于t2表是被驱动表,所以可能被读取多次,这里的read_cost和eval_cost是访问多次t2表后累加起来的值,主要关注prefix_cost的值代表的是整个连接查询预计的成本,也就是单次查询t1表和多次查询t2表后的成本的和,也就是:

prefix_cost(t1表) + read_cost(t2表) + eval_cost(t2表) = 总成本
2095.20 + 999.10 + 199.82 = 3294.12

所以最终求得执行查询的总成本是:3294.12。

 

TREE格式的执行计划

同样地,在EXPLAIN(DESC或DESCRIBE)和真正的查询语句中间加上FORMAT=TREE。这样我们就可以得到一个tree格式的执行计划,里边儿包含该计划花费的成本。但是TREE格式的执行计划只有MySQL 8.0后才支持。

mysql> DESC FORMAT=TREE SELECT * FROM student t1 INNER JOIN student2 t2 ON t1.address = t2.address WHERE t1.address LIKE '北京市%'\G
*************************** 1. row ***************************
EXPLAIN: -> Filter: (t2.address = t1.address) (cost=4.95 rows=3)
    -> Inner hash join (<hash>(t2.address)=<hash>(t1.address)) (cost=4.95 rows=3)
        -> Table scan on t2 (cost=0.23 rows=16)
        -> Hash
            -> Filter: (t1.address like '北京市%') (cost=1.85 rows=2)
                -> Table scan on t1 (cost=1.85 rows=16)

1 row in set (0.00 sec)

TREE格式的执行计划将查询的每一个步骤都分解成树型进行显示,由上可知,执行这个查询的总成本是:4.95。

 

 

 

执行计划扩展信息

 

 

 

我们在使用EXPLAIN(DESC或DESCRIBE)查看某个查询的执行计划后,还可以使用SHOW WARNINGS语句查看与这个查询的执行计划有关的一些扩展信息。

EXPLAIN SELECT t1.key1, t2.key1 FROM t1 LEFT JOIN t2 ON t1.key1 = t2.key1 WHERE t2.common_field IS NOT NULL;
SHOW WARNINGS\G

 

可以看到SHOW WARNINGS展示出来的信息有三个字段,分别是Level、Code、Message。我们最常见的就是Code为1003的信息,当Code值为1003时,Message字段展示的信息类似于查询优化器将我们的查询语句重写后的语句。比如我们上边的查询本来是一个左(外)连接查询,但是有一个t2.common_field IS NOT NULL的条件,就会导致查询优化器把左(外)连接查询优化为内连接查询,从SHOW WARNINGS的Message字段也可以看出来,原本的LEFT JOIN已经变成了JOIN。

小提示Message字段展示的信息类似于查询优化器将我们的查询语句重写后的语句,并不是等价于。也就是说Message字段展示的信息并不是标准的查询语句,在很多情况下重写后的语句并不能直接运行,它只能作为帮助我们理解MySQL将如何执行查询语句的一个参考依据而已

 

 

小结

 

 




通过两篇文章的篇幅详细学习了如何解读执行计划。通过执行计划,我们可以快速获取到一个查询的执行过程,并且可以快速定位到SQL的性能瓶颈所在,通过创建合理的索引和适当的改写,以达到优化SQL的目的。同时,我们还学习了FORMAT=JSON格式的执行计划,JSON格式的执行计划为我们提供了一种查看某个执行计划花费的成本的方式,使执行计划查看起来更加直观。同时我也简单介绍了一下TREE格式的执行计划和执行计划的扩展信息,为帮助我们理解MySQL将如何执行查询语句的一个参考依据。站在巨人肩膀上,每天进步一点点。

 

 参考资料 

 

https://dev.mysql.com/doc/refman/5.7/en/explain-output.html#explain_extra

小孩子4919《MySQL是怎样运行的:从根上理解MySQL》

 

 

end

 

收录于合集 #MySQL

 63个

上一篇线上环境MySQL数据误删一例下一篇MySQL之执行计划详解(一)

文章已于2022-01-05修改

喜欢此内容的人还喜欢

LinuxC++MySQL连接池的实现

 

Linux服务器开发

不喜欢

不看的原因

确定

  • 内容质量低
  • 不看此公众号

 

MyBatis多条件查询、动态SQL、多表操作、注解开发详细教程

 

雨落无影

不喜欢

不看的原因

确定

  • 内容质量低
  • 不看此公众号

 

SQL学习总结(31)—— instr查询字符串出现下标 + sql写函数 + 灵活设置联结条件2

 

札记随笔

不喜欢

不看的原因

确定

  • 内容质量低
  • 不看此公众号

 

 

 

小结

 

 




今天篇幅有点长,我们分开两部分来学习。今天的内容在掌握前面文章的基础上非常好理解,毕竟是MySQL提供给我们诊断SQL的工具,我们只要会使用即可,知道每一项输出的含义可以帮我们快速定位SQL语句的性能瓶颈。我们下篇见!~

 

 

 

 

 

 

##########sample 0

感谢   

 

https://blog.csdn.net/weixin_41558728/article/details/81704916

使用explain关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的,分析你的查询语句或是表结构的性能瓶颈。

explain执行计划包含的信息

这里写图片描述

其中最重要的字段为:id、type、key、rows、Extra

各字段详解

id

select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序 
三种情况: 
1、id相同:执行顺序由上至下 
这里写图片描述

2、id不同:如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行 
这里写图片描述

3、id相同又不同(两种情况同时存在):id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行 
这里写图片描述

select_type

查询的类型,主要是用于区分普通查询、联合查询、子查询等复杂的查询

1、SIMPLE:简单的select查询,查询中不包含子查询或者union 
2、PRIMARY:查询中包含任何复杂的子部分,最外层查询则被标记为primary 
3、SUBQUERY:在select 或 where列表中包含了子查询 
4、DERIVED:在from列表中包含的子查询被标记为derived(衍生),mysql或递归执行这些子查询,把结果放在零时表里 
5、UNION:若第二个select出现在union之后,则被标记为union;若union包含在from子句的子查询中,外层select将被标记为derived 
6、UNION RESULT:从union表获取结果的select 
这里写图片描述

type

访问类型,sql查询优化中一个很重要的指标,结果值从好到坏依次是:

system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

一般来说,好的sql查询至少达到range级别,最好能达到ref

1、system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现,可以忽略不计

2、const:表示通过索引一次就找到了,const用于比较primary key 或者 unique索引。因为只需匹配一行数据,所有很快。如果将主键置于where列表中,mysql就能将该查询转换为一个const 
这里写图片描述

3、eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键 或 唯一索引扫描。 
这里写图片描述
注意:ALL全表扫描的表记录最少的表如t1表

4、ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质是也是一种索引访问,它返回所有匹配某个单独值的行,然而他可能会找到多个符合条件的行,所以它应该属于查找和扫描的混合体 
这里写图片描述

5、range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了那个索引。一般就是在where语句中出现了bettween、<、>、in等的查询。这种索引列上的范围扫描比全索引扫描要好。只需要开始于某个点,结束于另一个点,不用扫描全部索引 
这里写图片描述

6、index:Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常为ALL块,应为索引文件通常比数据文件小。(Index与ALL虽然都是读全表,但index是从索引中读取,而ALL是从硬盘读取) 
这里写图片描述

7、ALL:Full Table Scan,遍历全表以找到匹配的行 
这里写图片描述

possible_keys

查询涉及到的字段上存在索引,则该索引将被列出,但不一定被查询实际使用

key

实际使用的索引,如果为NULL,则没有使用索引。 
查询中如果使用了覆盖索引,则该索引仅出现在key列表中 
这里写图片描述
这里写图片描述

key_len

表示索引中使用的字节数,查询中使用的索引的长度(最大可能长度),并非实际使用长度,理论上长度越短越好。key_len是根据表定义计算而得的,不是通过表内检索出的

ref

显示索引的那一列被使用了,如果可能,是一个常量const。

rows

根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数

Extra

不适合在其他字段中显示,但是十分重要的额外信息

1、Using filesort : 
mysql对数据使用一个外部的索引排序,而不是按照表内的索引进行排序读取。也就是说mysql无法利用索引完成的排序操作成为“文件排序” 
这里写图片描述
由于索引是先按email排序、再按address排序,所以查询时如果直接按address排序,索引就不能满足要求了,mysql内部必须再实现一次“文件排序”

2、Using temporary: 
使用临时表保存中间结果,也就是说mysql在对查询结果排序时使用了临时表,常见于order by 和 group by 
这里写图片描述

3、Using index: 
表示相应的select操作中使用了覆盖索引(Covering Index),避免了访问表的数据行,效率高 
如果同时出现Using where,表明索引被用来执行索引键值的查找(参考上图) 
如果没用同时出现Using where,表明索引用来读取数据而非执行查找动作 
这里写图片描述
覆盖索引(Covering Index):也叫索引覆盖。就是select列表中的字段,只用从索引中就能获取,不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。 
注意: 
a、如需使用覆盖索引,select列表中的字段只取出需要的列,不要使用select * 
b、如果将所有字段都建索引会导致索引文件过大,反而降低crud性能

4、Using where : 
使用了where过滤

5、Using join buffer : 
使用了链接缓存

6、Impossible WHERE: 
where子句的值总是false,不能用来获取任何元祖 
这里写图片描述

7、select tables optimized away: 
在没有group by子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段在进行计算,查询执行计划生成的阶段即可完成优化

8、distinct: 
优化distinct操作,在找到第一个匹配的元祖后即停止找同样值得动作

综合Case

这里写图片描述

执行顺序 
1(id = 4)、【select id, name from t2】:select_type 为union,说明id=4的select是union里面的第二个select。

2(id = 3)、【select id, name from t1 where address = ‘11’】:因为是在from语句中包含的子查询所以被标记为DERIVED(衍生),where address = ‘11’ 通过复合索引idx_name_email_address就能检索到,所以type为index。

3(id = 2)、【select id from t3】:因为是在select中包含的子查询所以被标记为SUBQUERY。

4(id = 1)、【select d1.name, … d2 from … d1】:select_type为PRIMARY表示该查询为最外层查询,table列被标记为 “derived3”表示查询结果来自于一个衍生表(id = 3 的select结果)。

5(id = NULL)、【 … union … 】:代表从union的临时表中读取行的阶段,table列的 “union 1, 4”表示用id=1 和 id=4 的select结果进行union操作。