EXPLAIN语句提供有关MySQL如何执行语句的信息。 EXPLAIN 可以用在 SELECT, DELETE, INSERT, REPLACE,和 UPDATE语句中,对于SELECT 语句,可以帮助我们写出更优的sql 。
注: 在mysql 5.6 前的版本中,explain 只支持 SELECT 语句。
下面从一个具体的栗子,开始 mysql explain 的详解。
mysql> explain select * from dye_orders where id > 1000 ;
+----+-------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | dye_orders | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 1078 | 100.00 | Using where |
+----+-------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.02 sec)
mysql>
注:博主 mysq l环境:mysql 5.7
下面将输出行信息一一详解:
1. id 列
这一列总是包含一个编号,标识 SELECT所属的行。如果在语句当中没有子査询或联合,那么只会有唯一的 SELECT,于是每一行在这个列中都将显示一个1。
当有子查询或联合的时候,就会顺序编号。
mysql> explain select (select 1 from dye_orders limit 1) from new_sales_orders;
+----+-------------+------------------+------------+-------+---------------+---------------------------------------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------------+------------+-------+---------------+---------------------------------------------+---------+------+------+----------+-------------+
| 1 | PRIMARY | new_sales_orders | NULL | index | NULL | new_sales_orders_quotation_order_id_foreign | 5 | NULL | 2973 | 100.00 | Using index |
| 2 | SUBQUERY | dye_orders | NULL | index | NULL | dye_orders_sales_order_id_foreign | 5 | NULL | 2157 | 100.00 | Using index |
+----+-------------+------------------+------------+-------+---------------+---------------------------------------------+---------+------+------+----------+-------------+
2. select type 列
对于该列,可选有以下几个值:
- SIMPLE (简单SELECT,不使用UNION或子查询等)
- PRIMARY (查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY)
- UNION(UNION中的第二个或后面的SELECT语句)
- DEPENDENT UNION (UNION中的第二个或后面的SELECT语句,取决于外面的查询)
- UNION RESULT (UNION的结果)
- SUBQUERY (子查询中的第一个SELECT)
- DEPENDENT SUBQUERY (子查询中的第一个SELECT,取决于外面的查询)
- DERIVED (派生表的SELECT, FROM子句的子查询)
- MATERIALIZED (物化子查询 ,注:官方文档:Materialized subquery)
- UNCACHEABLE UNION (union 结果集不能被缓存)
- UNCACHEABLE SUBQUERY (一个子查询的结果不能被缓存)
3. table 列
通常情况下,该值显而易见,是输出行所用表。而有时又不是:
mysql> explain select * from dye_orders union select * from dye_orders;
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| 1 | PRIMARY | dye_orders | NULL | ALL | NULL | NULL | NULL | NULL | 2157 | 100.00 | NULL |
| 2 | UNION | dye_orders | NULL | ALL | NULL | NULL | NULL | NULL | 2157 | 100.00 | NULL |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
3 rows in set, 1 warning (0.00 sec)
4. partitions 列
匹配到的分区,在非分区表中,会显示NULL。
5. type 列
官方手册给的是联接类型,但访问方式更合适些( 来源于高性能mysql )--MYSQL决定如何查找表中的行。下面是最重要的访问方法,依次从最差到最优。
5.1 ALL
这就是所谓的全表扫描,通常意味着MYSQL必须扫描整张表,从头到尾,去找到需要的行。
mysql> explain select * from dye_orders;
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | dye_orders | NULL | ALL | NULL | NULL | NULL | NULL | 2157 | 100.00 | NULL |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql>
5.2 index
这个跟全表扫描一样,只是MYSQL扫描表是索引次序而不是行。它的主要优点是避免了排序,最大缺点的是要承担按索引次序读取整个表的开销。这通常意味着若是按随机次序访问行,开销将会非常大。
如果在extra列中看到"using index",说明MYSQL正在使用覆盖索引,扫描它只索引的数据,而不是按索引次序的每一行。它比按索引次序全表扫描的开销要少很多。
5.3 range
范围扫描就是一个有限制的索引扫描,它开始于索引里的某一点,返回匹配这个值域的行。这比全索引扫描好一些,因为它用不着遍历全部索引。显而易见的范围描扫是带有BETWEEN或在WHERE子句里带有>查询的。
mysql> explain select id ,sales_order_id, currency_id, payment_term_id from dye_orders where id < 1000;
+----+-------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | dye_orders | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 1078 | 100.00 | Using where |
+----+-------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql>
5.4 ref
这是一种索引访问(有时也叫做索引查找),它返回所有匹配某个单个值的行。然而,它可能会找到多个符合条件的行,因此,它是查找和扫描的混合体。此类索引访问只有当使用非唯一性索引或者唯一性索引的非唯一性前缀时才会发生。把它叫做ref是因为索引要跟某个参考值相比较。这个参考值或者是一个常数,或者来是自多表查询前一个表里的结果值。
ref_or_null是ref之上的一个变体,它意味着MYSQL必须在初次查找的查询结果里进行第二次查找以找出NULL条目。
5.4.1 eq_ref
使用这种索引查找,MYSQL知道最多只返回一条符合条件的记录。这种访问方法可以在MYSQL使用主键或者唯一性索引查找时看到,它会将它们与某个参考值做比较。 MYSQL对于这类访问类型的优化做得非常好,因为它知道无须估计匹配行的范围或在找到匹配行后再继续查找。
5.4.2 const,system
当MYSQL能对查询的某部分进行优化并将其转换成一个常量时,它就会使用这些访问类型。举例来说,如果你通过将某一行的主键放入WHERE里的方式来选取此行的主键,MYSQL就能把这个查询转换为一个常量。然后就可以高效地将表从联接执行中移除。
mysql> explain select * from dye_orders where id = 1;
+----+-------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | dye_orders | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
5.4.3 NULL (mysql 5.7 官方文档中并没有提及,高性能mysql 中有提到)
这种访问方式意味着MySQL 能在优化阶段分解查询语句,在执行阶段甚至用不着再访问表或者索引。例如,从一个索引里选取最小值可以通过单独查找索引来完成,不需要在执行时访问表。
mysql> explain select * from dye_orders where id = 0;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | no matching row in const table |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+
1 row in set, 1 warning (0.01 sec)
mysql>
关于更详细的 type 列信息,参见:https://dev.mysql.com/doc/refman/5.7/en/explain-output.html#explain-join-types
6. possible_keys列
这一列显示了查询可以使用哪些索引,这是基于查询访问的列和使用的比较操作符来判断的。这个列表是在优化过程的早期创建的,因此有些罗列出来的索引可能对于后续化过程的是没用的。
7. key 列
这一列显示了MYSQL决定采用哪个索引来优化对该表的访问。如果该索引没有出现在possible_keys列中,那么MYSQL选用它是出于另外的原因----例如,它选择可能了个覆盖索引,哪怕没有WHERE子句。
换句话说,possible_keys揭示了哪一个索引能有助于高效地行查找,而key显示的是优化采用哪一个索引可以最小化查询成本。
mysql> explain select sales_order_id from dye_orders \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: dye_orders
partitions: NULL
type: index
possible_keys: NULL
key: dye_orders_sales_order_id_foreign
key_len: 5
ref: NULL
rows: 2157
filtered: 100.00
Extra: Using index
1 row in set, 1 warning (0.00 sec)
如果强制 mysql 去使用或者忽略一个 possible_keys 列出来的索引,在查询中可以使用 index 或者 ignore index 。
8. key_len列
该列显示了MYSQL在索引里使用的字节数。如果MYSQL正在使用的只是索引里某的些列,那么就可以用这个值来算出具体是哪些列。要记住,MYSQL5.5之前及版本只能使用索引的最左前缀。
mysql> create table t(
-> a char(3) not null,
-> b int(11) not null,
-> c char(1) not null ,
-> primary key (a,b,c)
-> )engine = myisam default charset = utf8;
Query OK, 0 rows affected (0.09 sec)
mysql> insert into t(a,b,c)
-> SELECT DISTINCT LEFT(TABLE_SCHEMA,3) ,ORD(TABLE_NAME),LEFT(COLUMN_NAME,1)
-> FROM INFORMATION_SCHEMA.COLUMNS;
Query OK, 786 rows affected (1.10 sec)
Records: 786 Duplicates: 0 Warnings: 0
mysql> EXPLAIN SELECT A FROM t WHERE a = 'sak' and b = 112;
+----+-------------+-------+------------+------+---------------+---------+---------+-------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+---------+---------+-------------+------+----------+-------------+
| 1 | SIMPLE | t | NULL | ref | PRIMARY | PRIMARY | 13 | const,const | 1 | 100.00 | Using index |
+----+-------------+-------+------------+------+---------------+---------+---------+-------------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql>
这个查询中平均长度为13字节,即为a 列 和 b 列的总长度。a 列是3个字符,utf8 下每一个最多为3个字节,而b 列是一个4字节整型。
MYSQL并不总显示一个索引真正使用了多少。例如,如果对一个前缀模式匹配执行 LTKE 查询,它会显示列的完全宽度正在被使用。
key_len 列显示了在索引字段中可能的最大长度,而不是表中数据使用的实际字节数。在前面例子中 MYSQL总是显示13字节,即使a列恰巧只包含一个字符长度。换言之,key len通过查找表的定义而被计算出,而不是表中的数据。
9. ref 列
这一列显示了之前的表在key列记录的索引中査找值所用的列或常量。
10. rows 列
这一列是 MYSQL估计为了找到所需的行而要读取的行数。这个数字是内嵌循环关联计划里的循环数目。也就是说它不是 MYSQL认为它最终要从表里读取出来的行数,而是MYSQL为了找到符合査询的每一点上标准的那些行而必须读取的行的平均数。
mysql> explain select * from dye_orders where id in (select id from dye_orders where id > 1500) ;
+----+-------------+------------+------------+--------+---------------+---------+---------+-----------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+--------+---------------+---------+---------+-----------------------+------+----------+-------------+
| 1 | SIMPLE | dye_orders | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 1078 | 100.00 | Using where |
| 1 | SIMPLE | dye_orders | NULL | eq_ref | PRIMARY | PRIMARY | 4 | sfabric.dye_orders.id | 1 | 100.00 | Using index |
+----+-------------+------------+------------+--------+---------------+---------+---------+-----------------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
根据表的统计信息和索引的选用情况,这个估算可能很不精确。在 MYSQL5.0及更早的版本里,它也反映不出 LIMIT 子句。举例来说,下面这个査询不会真的检查2157行。
mysql> explain select * from dye_orders limit 1;
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | dye_orders | NULL | ALL | NULL | NULL | NULL | NULL | 2157 | 100.00 | NULL |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
要记住这个数字是 MYSQL 认为它要检査的行数,而不是结果集里的行数。同时也要认识到有很多优化手段,例如关联缓冲区和缓存,无法影响到行数的显示。 MYSQL可能不必真的读所有它估计到的行,它也不知道任何关于操作系统或硬件缓存的信息。
11. filtered 列
它显示的是针对表里符合某个条件( WHERE子句或联接条件 )的记录数的百分比所做的一个悲观估算。如果你把 rows 列和这个百分比相乘,就能看到 MYSQL估算它将和查询计划里前一个表关联的行数。
12. Extra 列
这一列包含了关于mysql 如何解析查询的额外信息。
常见的最重要的值如下。
Using index
此值表示 MYSQL将使用覆盖索引,以避免访问表。
mysql> explain select id from dye_orders ;
+----+-------------+------------+------------+-------+---------------+-----------------------------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+-----------------------------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | dye_orders | NULL | index | NULL | dye_orders_sales_order_id_foreign | 5 | NULL | 2157 | 100.00 | Using index |
+----+-------------+------------+------------+-------+---------------+-----------------------------------+---------+------+------+----------+-------------+
using where
这意味着在存储引擎层面没有能把所有多余的行给过滤掉, MYSQL要在服务器层再进行过滤。
mysql> explain select id,code from dye_orders where id < 500 ;
+----+-------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | dye_orders | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 998 | 100.00 | Using where |
+----+-------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
Using temporary
这意味着 MYSQL 在对査询结果排序时会使用一个临时表。
mysql> explain select code from dye_orders where id < 500 group by code \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: dye_orders
partitions: NULL
type: range
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: NULL
rows: 998
filtered: 100.00
Extra: Using where; Using temporary; Using filesort
如果查询包含不同字段的group by 和 order by ,则通常会发生这种情况
Using filesort
这意味着 MYSQL 会对结果使用一个外部索引排序,而不是按索引次序从表里读取行。 MYSQL有两种文件排序算法,两种方式都可以在内存或磁盘上完成。 EXPLAIN不会告诉你 MYSQL将使用哪一种文件排序,也不会告诉你排序会在内存里还是磁盘上完成。
mysql> explain select id,code from dye_orders where id < 500 order by code;
+----+-------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-----------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-----------------------------+
| 1 | SIMPLE | dye_orders | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 998 | 100.00 | Using where; Using filesort |
+----+-------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-----------------------------+
注:当出现 Using filesort 就要注意下性能,前面说了,explain 不会告诉你是在内存还是在磁盘上完成排序,如非必要,建议排序操作放到业务层去做。
Range checked for each record (index map: N)
这个值意味着没有好用的索引,新的索引将在联接的每一行上重新估算。N是显示在 possible_keys 列中索引的位图,并且是冗余的。
至于更多的详细的 extra 信息,参见:https://dev.mysql.com/doc/refman/5.7/en/explain-output.html#explain-extra-information
注:explain 虽然列出了mysql 解析查询声称能的执行计划,但并不一定很准确。
文章参考自
《高性能mysql 第三版》 (下载地址:https://pan.baidu.com/s/1haFdY7c9xb6VNtlfPUaidQ)
mysql 官方手册: https://dev.mysql.com/doc/refman/5.7/en/explain-output.html