在 MySQL 中,EXPLAIN是一个用于分析查询语句执行计划的关键字。通过使用EXPLAIN关键字,可以在执行查询语句之前查看优化器生成的执行计划,而不会真正执行查询。执行计划提供了有关查询如何执行的信息,包括表的读取顺序、使用的索引、连接类型等。这对于理解查询的性能、优化查询以及发现潜在的性能问题非常有帮助。
EXPLAIN [PARTITIONS] SELECT ...
其中,PARTITIONS是可选的,用于显示有关分区的信息。如果查询中涉及到分区表,那么添加PARTITIONS选项将提供关于分区的详细信息。
以下是EXPLAIN语句输出中常见的字段及其含义:
一、`id`:表示查询中每个操作的标识符。它按照执行顺序分配给每个操作。
二、`select_type`:表示查询的类型,例如`SIMPLE`表示简单查询,`PRIMARY`表示主查询,`SUBQUERY`表示子查询等。
三、`table`:表示查询中涉及的表名。
四、`partitions`:表示查询计划中涉及的分区数量。如果表没有分区,则该值为`NULL`。
五、 `type`:表示访问表的方式,例如`ALL`表示全表扫描,`index`表示索引扫描,`range`表示范围扫描等。
六、`possible_keys`:表示可能用于查询的索引列表。
七、`key`:表示实际用于查询的索引。
八、 `key_len`:表示索引中使用的字节数。
九、`ref`:表示在连接操作中引用的列或常量。
十、`rows`:表示估计的返回行数。
十一、 `filtered`:百分比值,剩下多少满足查询条件记录数量的比例。
十二、 `Extra`:包含有关查询执行的其他信息,例如使用的连接类型、文件排序、临时表等。
select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序,该字段通常与table字段搭配来分析。
#1.id相同,执行顺序从上到下。:id相同,执行顺序从上到下,搭配table列进行观察可知,执行顺序为t1->t3->t2。
#2.id不同,如果是子查询,id的序号会递增,id值越大执行优先级越高。:如果是子查询id的序号会递增,id值越大执行优先级越高,搭配table列可知,执行顺序为t3->t1->t2。
#3.id相同不同,同时存在。:id如果相同,可认为是同一组,执行顺序从上到下。在所有组中,id值越大执行优先级越高。所以执行顺序为t3->derived2(衍生表,也可以说临时表)->t2。
总结:id的值表示select子句或表的执行顺序,id相同,执行顺序从上到下,id不同,值越大的执行优先级越高。
查询的类型,主要用于区别普通查询、联合查询、子查询等复杂的查询。其值主要有六个:
#1.SIMPLE:简单的select查询,查询中不包含子查询或union查询。
#2.PRIMARY:查询中若包含任何复杂的子部分,最外层查询为PRIMARY,也就是最后加载的就是PRIMARY。
#3.SUBQUERY:在select或where列表中包含了子查询,就为被标记为SUBQUERY。
#4.DERIVED:在from列表中包含的子查询会被标记为DERIVED(衍生),MySQL会递归执行这些子查询,将结果放在临时表中。
#5.UNION:若第二个select出现在union后,则被标记为UNION,若union包含在from子句的子查询中,外层select将被标记为DERIVED。
#6.UNION RESULT:从union表获取结果的select。
官方定义为The matching partitions(匹配的分区),该字段应该是看table所在的分区吧(不晓得理解错误没)。值为NULL表示表未被分区。
表示查询所使用的访问类型,type的值主要有八种,该值表示查询的sql语句好坏,从最好到最差依次为:system>const>eq_ref>ref>range>index>ALL。
#1.system:表只有一行记录(等于系统表),是const的特例类型,平时不会出现,可以忽略不计。
但是笔者发现在MySQL5.7.22时,不会出现该字段值,只能出现const,但是在MySQL5.7版本以下可以出现该情况。猜测MySQL5.7版本是不是进行了优化,因为system官网的解释:注:两个引擎的执行信息不一样,5.5.48执行过程中产生了临时表(DERIVED),5.7.22为简单查询。
#2.const:表示通过一次索引就找到了结果,常出现于primary key或unique索引。因为只匹配一行数据,所以查询非常快。如将主键置于where条件中,MySQL就能将查询转换为一个常量。
注:对于system和const可能实际意义并不是很大,因为单表单行查询本来就快,意义不大。
#3.eq_ref:唯一索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见主键或唯一索引扫描。
注:经理只有一人,进行了tb_dept的主键扫描。
#4.ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,返回匹配某值(某条件)的多行值,属于查找和扫描的混合体。
由于是非唯一性索引扫描,所以对tb_emp表的deptid字段创建索引:
create index idx_tb_emp_deptid on tb_emp(deptid);
#5.range:只检索给定范围的行,使用一个索引来检索行,可以在key列中查看使用的索引,一般出现在where语句的条件中,如使用between、>、<、in等查询。这种索引的范围扫描比全表扫描要好,因为索引的开始点和结束点都固定,不用扫描全索引。虽然我们为deptid字段创建了索引并在where中使用了between等,但在如下情况type仍为ALL。
对比两图,可以看到使用deptid和id进行操作,其type的值一个是ALL也就是进行了全表扫描,一个是range进行了指定索引范围值检索。可能原因deptid并不是唯一索引。
对于以上问题,需要具体问题具体分析,并不能一概而论。
#6.index:全索引扫描,index和ALL的区别:index只遍历索引树,通常比ALL快,因为索引文件通常比数据文件小。虽说index和ALL都是全表扫描,但是index是从索引中读取,ALL是从磁盘中读取。
#7.ALL:全表扫描。
注:一般来说,需保证查询至少达到range级别,最好能达到ref。
显示可能应用在表中的索引,可能一个或多个。查询涉及到的字段若存在索引,则该索引将被列出,但不一定被查询实际使用。
实际中使用的索引,如为NULL,则表示未使用索引。若查询中使用了覆盖索引,则该索引和查询的select字段重叠。
表示索引中所使用的字节数,可通过该列计算查询中使用的索引长度。在不损失精确性的情况下,长度越短越好。key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,并不是通过表内检索出的。
显示关联的字段。如果使用常数等值查询,则显示const,如果是连接查询,则会显示关联的字段。
注:由于id相同,因此从上到下执行:
#1.tb_emp表为非唯一性索引扫描,实际使用的索引列为idx_name,由于tb_emp.name='rose'为一个常量,所以ref=const。
#2.tb_dept为唯一索引扫描,从sql语句可以看出,实际使用了PRIMARY主键索引,ref=db01.tb_emp.deptid表示关联了db01数据库中tb_emp表的deptid字段。
根据表统计信息及索引选用情况大致估算出找到所需记录所要读取的行数。当然该值越小越好。
百分比值,表示存储引擎返回的数据经过滤后,剩下多少满足查询条件记录数量的比例。
显示十分重要的额外信息。其取值有以下几个:
#1.Using filesort
Using filesort表明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。
mysql中无法利用索引完成的排序操作称为“文件排序”。
出现Using filesort就非常危险了,在数据量非常大的时候几乎“九死一生”。出现Using filesort尽快优化sql语句。
deptname字段未建索引的情况。
为deptname字段创建索引后。
#2.Using temporary
使用了临时表保存中间结果,常见于排序order by和分组查询group by。非常危险,“十死无生”,急需优化。
将tb_emp中name的索引先删除,出现如下图结果,非常烂,Using filesort和Using temporary,“十死无生”。
为name字段创建索引后。
#3.Using index
表明相应的select操作中使用了覆盖索引,避免访问表的额外数据行,效率不错。
如果同时出现了Using where,表明索引被用来执行索引键值的查找。(where deptid=1)
如果没有同时出现Using where,表明索引用来读取数据而非执行查找动作。
删除tb_emp表中name和deptid字段的单独索引,创建复合索引。
从这里给出覆盖索引的定义:select的数据列只从索引中就能取得数据,不必读取数据行。通过上面的例子理解:创建了(name,deptid)的复合索引,查询的时候也使用复合索引或部分,这就形成了覆盖索引。简记:查询使用复合索引,并且查询的列就是索引列,不能多,个数需对应。
使用优先级Using index>Using filesort(九死一生)>Using temporary(十死无生)。也就说出现后面两项表明sql语句是非常烂的,急需优化!!!
假设有一个名为`students`的表,包含以下列:
- `id`:学生编号,主键。
- `name`:学生姓名。
- `age`:学生年龄。
- `gender`:学生性别。
要查询年龄大于 20 岁的学生信息,可以使用以下查询语句:
SELECT * FROM students WHERE age > 20;
使用EXPLAIN关键字来分析该查询的执行计划:
EXPLAIN SELECT * FROM students WHERE age > 20;
这将返回以下执行计划信息:
+----+-------------+-------+------+---------------+------+---------+------+--------+------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | +----+-------------+-------+------+---------------+------+---------+------+--------+------+-------+ | 1 | SIMPLE | students | NULL | range | age | age | 5 | NULL | 10 | 100.00 | +----+-------------+-------+------+---------------+------+---------+------+--------+------+-------+
在这个执行计划中,我们可以看到以下信息:
- `id`为 1,表示查询中第一个操作的标识符。
- `select_type`为`SIMPLE`,表示这是一个简单查询。
- `table`为`students`,表示查询涉及的表名为`students`。
- `partitions`为`NULL`,表示表没有分区。
- `type`为`range`,表示使用了索引范围扫描来检索满足条件的行。
- `possible_keys`为`age`,表示可能用于查询的索引为`age`索引。
- `key`为`age`,表示实际使用的索引为`age`索引。
- `key_len`为 5,表示索引`age`的长度为 5 个字节。
- `ref`为`NULL`,表示在连接操作中没有引用任何列或常量。
- `rows`为 10,表示估计的返回行数为 10 行。
- `filtered`为 100.00,表示在表扫描之前,根据条件过滤掉的行数的百分比为 100%。
通过分析执行计划,我们可以了解查询的执行方式,包括使用的索引、表的读取顺序、连接类型等。这有助于我们优化查询,提高查询的性能。
1. EXPLAIN语句不会真正执行查询,而是生成执行计划信息。因此,它不会返回实际的查询结果,只会返回有关查询执行的信息。
2. 在分析查询计划时,需要注意以下几点:
- `type`字段表示访问表的方式。常见的类型包括`ALL`(全表扫描)、`index`(索引扫描)、`range`(范围扫描)等。尽量避免全表扫描,而是使用索引扫描或范围扫描来提高查询效率。
- `key`字段表示实际使用的索引。如果可能的话,尽量使用合适的索引来加速查询。
- `rows`字段表示估计的返回行数。这可以帮助你了解查询的规模和可能的性能影响。
- `filtered`字段表示在表扫描之前,根据条件过滤掉的行数的百分比。较高的过滤百分比表示查询在扫描表之前已经排除了许多不符合条件的行,这可以提高查询效率。
3. 在分析查询计划时,还可以考虑以下几点:
- 检查索引的选择是否正确。如果查询计划中显示没有使用索引,或者使用了不合适的索引,可以考虑创建或修改索引来优化查询。
- 检查连接类型。如果查询中涉及到多个表的连接,可以查看连接类型(例如`INNER JOIN`、`LEFT JOIN`等)是否符合预期。
- 分析子查询的性能。如果查询中包含子查询,可以查看子查询的执行计划,以确定是否存在性能问题。
4. 请注意,执行计划信息可能会受到查询优化器的影响,不同的优化器版本或配置可能会生成不同的执行计划。因此,在优化查询时,可能需要进行一些实验和调整,以找到最佳的执行计划。
通过使用EXPLAIN关键字和分析执行计划信息,可以深入了解查询的执行方式,发现潜在的性能问题,并采取相应的优化措施来提高查询的效率和性能。