MySQL - EXPLAIN详解

日常工作中,我们有时会通过日志记录下耗时较长的SQL语句,但是光找出这些SQL语句并不意味着完事了,常常需要借助 EXPLAIN来查看SQL语句的执行计划,查看SQL语句是否用上了索引,是否进行了全表扫描,这都可以通过 EXPLAIN命令得到。

概述

EXPLAIN:SELECT语句中使用到的每个表返回一条信息。它按照MySQL在处理语句时读取它们的顺序列出这些表。MySQL使用循环嵌套算法解析所有连接。意味着MySQL从第一个表中读取一行,然后在第二个表,第三个表中找到匹配的行,等等。

QEP: SQL语句的查询执行计划

注意:

在以前版本的MySQL中,使用 EXPLAIN PARTITIONSEXPLAIN EXTENDED 来生成分区和扩展信息 。目前为止这些语法仍然是向后兼容的,但未来 MySQL会将它们排除出 EXPLAIN语法,因为现在 EXPLAIN默认就会输出分区扩展的相关信息。所以 PARTITIONSEXTENDED关键字是多余的,不推荐使用,且在使用时会提示警告。

EXPLAIN 输出

本部分着重描述 EXPLAIN生成的结果。更多关于 typeExtra 列的信息会在下文一一的介绍 。

mysql> EXPLAIN SELECT * FROM customer;
+----+-------------+----------+------+---------------+------+---------+------+--------+-------+
| id | select_type | table    | type | possible_keys | key  | key_len | ref  | rows   | Extra |
+----+-------------+----------+------+---------------+------+---------+------+--------+-------+
|  1 | SIMPLE      | customer | ALL  | NULL          | NULL | NULL    | NULL | 936161 |       |
+----+-------------+----------+------+---------------+------+---------+------+--------+-------+
1 row in set

id(JSON名: select_id)

SELECT 标识符,SQL执行的顺序的标识,SQL从大到小的执行

  • id相同时,执行顺序由上至下
  • 如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
  • 如果id相同,则认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行

select_type(JSON名:无)

SELECT 类型,可以是下表显示中的任何类型。

table(JSON名: table_name)

mysql> EXPLAIN SELECT t1.* FROM (SELECT mobile FROM customer GROUP BY mobile) t1;
+----+-------------+------------+------+---------------+------+---------+------+--------+---------------------------------+
| id | select_type | table      | type | possible_keys | key  | key_len | ref  | rows   | Extra                           |
+----+-------------+------------+------+---------------+------+---------+------+--------+---------------------------------+
|  1 | PRIMARY     | <derived2> | ALL  | NULL          | NULL | NULL    | NULL | 936161 |                                 |
|  2 | DERIVED     | customer   | ALL  | NULL          | NULL | NULL    | NULL | 936161 | Using temporary; Using filesort |
+----+-------------+------------+------+---------------+------+---------+------+--------+---------------------------------+
2 rows in set

type(JSON名: access_type)

partitions(JSON名: partitions)

记录与查询匹配的分区。值为NULL表示为 非分区表(5.7才有)

possiblekeys(JSON名: possiblekeys)

表示MySQL查找表中的行时可选择的索引。请注意,此列完全独立于 EXPLAIN输出中显示的顺序。 这意味着在 possible_keys中的某些键实际上不能按生成的表顺序使用。

如果该列是NULL,则代表没有相关的索引。在这种情况下,可以通过检查 WHERE子句看它是否引用了某些列或适合索引的列来提高查询性能。如果是这样,那么就需要创造一个适当的索引,并再次用 EXPLAIN检查

key(JSON名:key)

显示MySQL实际决定使用的键(索引),如果MySQL决定使用其中一个 possible_keys 索引来查找行,则该索引被列为关键值。

如果没有选择索引,键是NULL。要想强制MySQL使用或忽视 possible_keys列中的索引,在查询中使用 FORCE INDEX、USE INDEX或者 IGNORE INDEX

对于 InnoDB而言,即便是查询也选择主键索引,辅助索引( secondary index)可能会覆盖所选列,因为InnoDB将主键值存储在每个辅助索引中。如果key为NULL,则代表MySQL未发现可用于提高效率的索引。

对于 MyISAM的表,运行 ANALYZE TABLE 有助于优化器选择更好的索引。 myisamchk--analyze 也是如此。

keylen(JSON名: keylength)

显示MySQL使用 索引键的长度。如果 key是NULL,则 key_len为NULL。使用的索引的长度。在不损失精确性的情况下,长度越短越好

ref(JSON名:ref)

被用来标识那些用来进行索引比较的列或者常量

rows (JSON名 : rows)

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

filtered(JSON名: filtered)

给出了一个百分比的值,这个百分比值和 rows 列的值一起使用。(5.7才有)

Extra (JSON名称:无)

MySQL的附加信息,提供了与操作有关联的信息

EXPLAIN JOIN Types 详解

下面将描述从最佳类型到最差类型的连接类型

system

该表只有一行数据。这是 const连接类型的特例

const

查询开始时读取,最多匹配出一行记录。由于只有一行,因此该行中列的值会被优化器 视为常量const速度非常快,因为它们只读一次。

示例代码:

SELECT * FROM tbl_name WHERE primary_key = 1;

SELECT * FROM tbl_name
  WHERE primary_key_part1 = 1 AND primary_key_part2 = 2;

eq_ref

效率仅次于 systemconst,可以用于 =运算符进行比较的索引列,比较值可以是一个常量,也可以是一个表达式。

示例代码:

SELECT * FROM ref_table,other_table
  WHERE ref_table.key_column = other_table.column;

SELECT * FROM ref_table,other_table
  WHERE ref_table.key_column_part1 = other_table.column
  AND ref_table.key_column_part2 = 1;

ref

ref可以用于使用 =、or <=> 运算符进行比较的索引列 。

示例代码:

SELECT * FROM ref_table WHERE key_column = expr;

SELECT * FROM ref_table,other_table
  WHERE ref_table.key_column = other_table.column;

SELECT * FROM ref_table,other_table
  WHERE ref_table.key_column_part1 = other_table.column
  AND ref_table.key_column_part2 = 1;

fulltext

查询时使用 FULLTEXT 索引。

refornull

该类型与 ref 类似,不同的是,它还对包含 NULL的行进行额外的搜索。常作用在解析子查询中。

示例代码:

SELECT * FROM ref_table
  WHERE key_column = expr OR key_column IS NULL;

index_merge

此连接类型表示使用的是索引合并优化

示例代码:

SELECT * FROM tbl_name WHERE key1 = 10 OR key2 = 20;

SELECT * FROM tbl_name
  WHERE (key1 = 10 OR key2 = 20) AND non_key = 30;

SELECT * FROM t1, t2
  WHERE (t1.key1 IN (1,2) OR t1.key2 LIKE 'value%')
  AND t2.key1 = t1.some_col;

SELECT * FROM t1, t2
  WHERE t1.key1 = 1
  AND (t2.key1 = t1.some_col OR t2.key2 = t1.some_col2);

缺陷:

  • 全文索引不适用于合并。
  • MySQL不会选择包含多层 AND/ OR 嵌套的复杂子句(修复方式如下)。
(x AND y) OR z => (x OR z) AND (y OR z)
(x OR y) AND z => (x AND z) OR (y AND z)

unique_subquery

只是一个索引查找函数,可以完全替代子查询以提高效率。

示例代码:

value IN (SELECT primary_key FROM single_table WHERE some_expr)

index_subquery

这种连接类型与 unique_subquery类似,取代了IN子查询。

示例代码:

value IN (SELECT key_column FROM single_table WHERE some_expr)

range

只检索在给定范围内的行。输出行中的列指出使用的具体索引。这个类型的ref列是NULL。

示例代码:

SELECT * FROM tbl_name
  WHERE key_column = 10;

SELECT * FROM tbl_name
  WHERE key_column BETWEEN 10 and 20;

SELECT * FROM tbl_name
  WHERE key_column IN (10,20,30);

SELECT * FROM tbl_name
  WHERE key_part1 = 10 AND key_part2 IN (10,20,30);

index

索引连接类型与ALL相同,只是索引树被扫描了。当查询只使用到单个索引的部分列时,MySQL就会使用这种 JoinTypes。主要体现在两个方面:

  • 如果查询索引被覆盖了,且满足表中所需的所有数据,这时只扫描索引树。它比ALL扫描的要快,因为索引树比表数据小很多。Extra 列中会给出 Usingindex字眼的信息。
  • 使用索引读取数据,以索引顺序查找数据行,进行完整的表扫描。使用的索引信息不会出现在 Extra列中。

ALL

全表扫描,性能最糟,可以通过添加索引来避免。

EXPLAIN Extra 详解

一下列表表示可能出现在 Extra中的值。如果要尽可能快的查询,那么了解下面内容是不错的选择。

const row not found(JSON属性: constrownot_found)

对空表做类似 SELECT...FROM tbl_name 的查询操作

Deleting all rows(JSON属性: message)

使用 DELETE时,某些存储引擎( MyISAM)支持的一些 简单、快速的处理方法。如果引擎使用到此类优化就会显示该内容

Distinct(JSON属性: distinct)

去重搜索是会显示出该内容

FirstMatch(tblname) (JSON属性:firstmatch)

表示 tbl_name 使用的半连接的 FirstMatch连接策略。

Full scan on NULL key (JSON property: message)

当查询优化器不能使用索引查询时,那么查询优化后执行回退策略。

Impossible HAVING(JSON属性: message)

HAVING条件过滤没有效果,或者是始终选不出任何列(理解为返回已有查询的结果集)。

Impossible WHERE (JSON属性:message)

WHERE条件过滤没有效果,或者是始终选不出任何列(理解为最终是全表扫描)。

Impossible WHERE noticed after reading const tables (JSON属性:message)

查询了所有const(常量表和系统表),但发现 WHERE查询条件不起作用。

LooseScan(m..n) (JSON属性:message)

使用半连接LooseScan策略。 m 和 n是索引部分的数量

No matching min/max row(JSON属性: message)

没有行满足查询的条件,如 SELECT MIN(...)FROM...WHERE condition

No matching row in const table(JSON属性:message)

对于连接查询,列未满足唯一索引的条件或表为空。

No matching rows after partition pruning(JSON属性: message)

对于DELETE 或 UPDATE,优化器在分区之后,未发现任何要删除或更新的内容。类似查询 ImpossibleWHERE

No tables used(JSON属性: message)

查询没有FROM子句,或者有一个 FROM DUAL子句。

Not exists(JSON属性: message)

MySQL能够对 LEFT JOIN查询进行优化,并且在查找到符合 LEFT JOIN条件的行后,则不再查找更多的行。

示例代码:

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

假定t2.id被定义为 NOT NULL。在这种情况下,MySQL 使用t1.id列的值查找t2表中的行 。如果找到匹配的行,且知道 t2.id不可能是 NULL,那么将不在继续查找t2表中剩余id相同的行。换句话说,对于每一行,MySQL只需要进行一次查询,而不管有多少行够与其匹能对应

Plan isn't ready yet (JSON属性:无)

这个值的产生在 EXPLAIN FOR CONNECTION,当优化器不能按照被命名的查询连接来创建一个执行器计划时就会出现 Planisn't ready yet。如果执行计划的输出包含了多行,所有行都可以有该值,则取决于优化器来决定完整的执行计划。

Range checked for each record (index map: N)(JSON属性: message)

MySQL没有发现可以使用的较好的索引,但是发现一些索引也许能使用在已有表的列值上。对于已有表格数据的每一行比较,检查是否可以使用 rangeindex_merge 方法来检索行。虽然不是最快的,但也比完全不用索引要快的多。

Scanned N databases(JSON属性: message)

表示处理 INFORMATION_SCHEMA表查询时服务器执行的扫描次数。关于N的值可以是0,1,或者是all.

Start temporary,End temporary(JSON属性: message)

说明在半连接复制清除策略中使用了临时表

unique row not found(JSON属性: message)

对于类似于 SELECT...FROM tbl_name的查询,表中找不到满足条件唯一索引或主键索引的列。

Using filesort(JSON属性: using_filesort)

MySQL必须做一个额外的传递才能找出按排序的顺序检索数据。通过连接类型存储的排序关键字和 WHERE查询条件等一起确定的。然后对键进行排序,并按排序顺序检索行。

Using index(JSON属性: using_index)

只需通过索引树就可以从表中获取列的信息,无需额外去读取真实的行数据。如果查询使用的列值仅仅是一个简单索引的部分值,则会使用这种策略来优化查询。对于innoDB数据库中的表有一个自定义的聚簇索引,该索引能够起作用,即使是Using index并没有出现在Extra列中。这种情况下的type字段为index并且key字段的值为PRIMARY。

Using index condition(JSON属性: usingindexcondition)

表的读取首先通过读入索引值来判断是否需要全表扫描。在这种方式中,如果有需要的话。索引信息将被用来服务(压入)全表扫描的。

Using index for group-by(JSON属性:usingindexforgroupby)

类似于 Usingindex的表查询方法,指 MySQL发现索引能够被用来查找 groupbyDISTINCT的列,而不需要任何真实的表查询。另外,索引使得每个分组查找都更有效,只有少量的索引值需要读取。

Using join buffer (Block Nested Loop), Using join buffer (Batched Key Access) (JSON属性:usingjoinbuffer)

从已有连接中找被读入缓存的数据,并且通过缓存来完成与当前表的连接。(Block Nested Loop)说明使用了块循环算法,(Batched key Access)说明使用了批量接入关键字算法。也就是说,在 EXPLAIN输出记录中,从已经查找过的表中将输出的列缓存下来,并在需要时批量的找出与当前数据对比,这时就会出现 Usingjoin buffer

在JSON格式的输出中, using_join_buffer的值要么是 BlockNestedLoop,要么是 BatchedKeyAccess.

Using MRR(JSON属性: message)

使用多范围读取的优化策略来读取表中的数据。

示例代码:(假设有一个索引: (key_part1, key_part2))

SELECT * FROM t
  WHERE key_part1 >= 1000 AND key_part1 < 2000
  AND key_part2 = 10000;

对于MRR,通过配置系统变量 read_rnd_buffer_size来作为它的缓冲区,并通过它来确定每次最大处理字节数。

Using sort_union(...),Using union(...),Using intersect(...)(JSON属性: message)

表示在 index_merge的连接类型中索引合并是怎么样完成的,及使用了怎样特别的算法。

Using temporary(JSON属性: usingtemporarytable)

为了执行查询,MySQL需要创建一个临时表来存储已有的结果。如果发现查询中 groupbyorderby是不同的列,则会有该类型产生。

Using where(JSON属性: attached_condition)

WHERE条件用于赛选出与下一个表匹配的数据然后返回给客户端。除非故意做的全表扫描,否则连接类型是 ALL或者是 index,且在 Extra列的值中没有 UsingWhere,则该查询可能是有问题的。

Using where with pushed condition(JSON属性:message)

该内容只适用在 NDB的表中。意味着NDB集群中正在使用“pushed down”优化策略,保证了通过网络只发送有用的数据,且比未优化的情况下提高了5-10倍的速度。

Zero limit(JSON属性: message)

查询条件中有 LIMIT0 并且没有任何可以选择的记录。

原文发布于微信公众号 - battcn(battcn)

原文发表时间:2017-12-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏待你如初见

Day15

983
来自专栏Linyb极客之路

深入理解MySQL索引原理和实现——为什么索引可以加速查询?

说到索引,很多人都知道“索引是一个排序的列表,在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址,在数据十分庞大的时候,索引可以大大加快查询的速度,这...

1313
来自专栏java一日一条

浅谈SQL语句的执行过程

写在前面的话:有时不理解SQL语句各个部分执行顺序,导致理解上出现偏差,或者是书写SQL语句时随心所欲,所以有必要了解一下sql语句的执行顺序。可以有时间自己写...

722
来自专栏程序猿

Mysql_基础

一、 简单查询 简单的Transact-SQL查询只包括选择列表、FROM子句和WHERE子句。它们分别说明所查询列、查询的 表或视图、以及搜索条件等。 例...

3537
来自专栏互联网开发者交流社区

数据定义: CREATE、DROP、ALTER

1362
来自专栏zingpLiu

python【第十二篇】Mysql基础

数据库(Database)是按照数据结构来组织、存储和管理数据的仓库,每个数据库都有一个或多个不同的API用于创建,访问,管理,搜索和复制所保存的数据。我们也可...

962
来自专栏Java后端生活

MySQL(十二)DML数据的增删改

1546
来自专栏后台日记

Mysql INSERT ON DUPLICATE KEY UPDATE

INSERT ... ON DUPLICATE KEY UPDATE是MySQL insert的一种扩展。当发现有重复的唯一索引(unique key)或者主键...

1423
来自专栏数据和云

层层升入:SQL极限调优之一次更新操作的N种优化可能

杨廷琨,网名 yangtingkun 云和恩墨技术总监,Oracle ACE Director,ACOUG 核心专家 最近进行了一次更新操作,整个处理和优化的过...

3028
来自专栏文渊之博

mysql表分区简述

数据库分区是一种物理数据库设计技术。虽然分区技术可以实现很多效果,但其主要目的是为了在特定的SQL操作中减少数据读写的总量以缩减sql语句的响应时间,同时对于应...

1043

扫码关注云+社区