MYSQL 之EXPLAIN 语法

EXPLAINtbl_name

或者: mysql

EXPLAIN SELECTselect_options

EXPLAIN 语句能够被看成 DESCRIBE 的同义词来用,也能够用来获取一个MySQL要执行的 SELECT 语句的相关信息。 sql

  • EXPLAIN tbl_name 语法和 DESCRIBE tbl_name 或 SHOW COLUMNS FROM tbl_name同样。
  • 当在一个 SELECT 语句前使用关键字 EXPLAIN 时,MYSQL会解释了即将如何运行该 SELECT 语句,它显示了表如何链接、链接的顺序等信息。

本章节主要讲述了第二种 EXPLAIN 用法。 函数

在 EXPLAIN 的帮助下,您就知道何时该给表添加索引,以使用索引来查找记录从而让 SELECT 运行更快。 性能

若是因为不恰当使用索引而引发一些问题的话,能够运行 ANALYZE TABLE 来更新该表的统计信息,例如键的基数,它能帮您在优化方面作出更好的选择。详情请看"ANALYZE TABLE Syntax"优化

您还能够查看优化程序是否以最佳的顺序来链接数据表。为了让优化程序按照 SELECT语句中的表名的顺序作链接,能够在查询的开始使用 SELECT STRAIGHT_JOIN 而不仅是SELECT。 spa

EXPLAIN 返回了一行记录,它包括了 SELECT 语句中用到的各个表的信息。这些表在结果中按照MySQL即将执行的查询中读取的顺序列出来。MySQL用一次扫描屡次链接(single-sweep, multi-join) 的方法来解决链接。这意味着MySQL从第一个表中读取一条记录,而后在第二个表中查找到对应的记录,而后在第三个表中查找,依次类推。当全部的表都扫描完了,它输出选择的字段而且回溯全部的表,直到找不到为止,由于有的表中可能有多条匹配的记录下一条记录将从该表读取,再从下一个表开始继续处理。 指针

在MySQL version 4.1中,EXPLAIN 输出的结果格式改变了,使得它更适合例如 UNION语句、子查询以及派生表的结构。更使人注意的是,它新增了2个字段: id 和select_type。当你使用早于MySQL 4.1的版本就看不到这些字段了。 code

EXPLAIN 结果的每行记录显示了每一个表的相关信息,每行记录都包含如下几个字段: 排序

id
本次 SELECT 的标识符。在查询中每一个 SELECT 都有一个顺序的数值。select_type
SELECT 的类型,可能会有如下几种:SIMPLE
简单的 SELECT (没有使用 UNION 或子查询)
PRIMARY
最外层的 SELECT。
UNION
第二层,在SELECT 以后使用了 UNION 。
DEPENDENT UNION
UNION 语句中的第二个 SELECT,依赖于外部子查询
SUBQUERY
子查询中的第一个 SELECT
DEPENDENT SUBQUERY
子查询中的第一个 SUBQUERY 依赖于外部的子查询
DERIVED
派生表 SELECT(FROM 子句中的子查询)
table
记录查询引用的表。type
表链接类型。如下列出了各类不一样类型的表链接,依次是从最好的到最差的:
system
表只有一行记录(等于系统表)。这是 const 表链接类型的一个特例。
const
表中最多只有一行匹配的记录,它在查询一开始的时候就会被读取出来。因为只有一行记录,在余下的优化程序里该行记录的字段值能够被看成是一个恒定值。const 表查询起来很是快,由于只要读取一次!const 用于在和PRIMARY KEY 或 UNIQUE 索引中有固定值比较的情形。下面的几个查询中,tbl_name 就是 const 表了:
SELECT * FROMtbl_nameWHEREprimary_key=1;
SELECT * FROMtbl_nameWHEREprimary_key_part1=1 ANDprimary_key_part2=2;
eq_ref
从该表中会有一行记录被读取出来以和从前一个表中读取出来的记录作联合。与 const 类型不一样的是,这是最好的链接类型。它用在索引全部部分都用于作链接而且这个索引是一个 PRIMARY KEY 或 UNIQUE 类型。eq_ref 能够用于在进行"="作比较时检索字段。比较的值能够是固定值或者是表达式,表达式中可使用表里的字段,它们在读表以前已经准备好了。如下的几个例子中,MySQL使用了 eq_ref 链接来处理 ref_table:

SELECT * FROMref_table,other_tableWHEREref_table.key_column=other_table.column;
SELECT * FROMref_table,other_tableWHEREref_table.key_column_part1=other_table.columnANDref_table.key_column_part2=1;
ref
该表中全部符合检索值的记录都会被取出来和从上一个表中取出来的记录做联合。ref 用于链接程序使用键的最左前缀或者是该键不是 PRIMARY KEY 或 UNIQUE索引(换句话说,就是链接程序没法根据键值只取得一条记录)的状况。当根据键值只查询到少数几条匹配的记录时,这就是一个不错的链接类型。ref 还能够用于检索字段使用 = 操做符来比较的时候。如下的几个例子中,MySQL将使用ref 来处理 ref_table:
SELECT * FROMref_tableWHEREkey_column=expr;
SELECT * FROMref_table,other_tableWHEREref_table.key_column=other_table.column;
SELECT * FROMref_table,other_tableWHEREref_table.key_column_part1=other_table.columnANDref_table.key_column_part2=1;
ref_or_null
这种链接类型相似 ref,不一样的是MySQL会在检索的时候额外的搜索包含 NULL值的记录。这种链接类型的优化是从MySQL 4.1.1开始的,它常常用于子查询。在如下的例子中,MySQL使用 ref_or_null 类型来处理 ref_table:
SELECT * FROMref_tableWHEREkey_column=exprORkey_columnIS NULL;


index_merge
这种链接类型意味着使用了 Index Merge 优化方法。这种状况下,key字段包括了全部使用的索引,key_len 包括了使用的键的最长部分。
unique_subquery
这种类型用例如一下形式的 IN 子查询来替换 ref:
valueIN (SELECTprimary_keyFROMsingle_tableWHEREsome_expr)

unique_subquery 只是用来彻底替换子查询的索引查找函数效率更高了。 索引

index_subquery
这种链接类型相似 unique_subquery。它用子查询来代替 IN,不过它用于在子查询中没有惟一索引的状况下,例如如下形式:
valueIN (SELECTkey_columnFROMsingle_tableWHEREsome_expr)
range
只有在给定范围的记录才会被取出来,利用索引来取得一条记录。key 字段表示使用了哪一个索引。key_len 字段包括了使用的键的最长部分。这种类型时 ref 字段值是 NULL。range 用于将某个字段和一个定植用如下任何操做符比较时 =, <>, >,>=, <, <=, IS NULL, <=>, BETWEEN, 或 IN:
SELECT * FROMtbl_nameWHEREkey_column= 10;

SELECT * FROMtbl_nameWHEREkey_columnBETWEEN 10 and 20;

SELECT * FROMtbl_nameWHEREkey_columnIN (10,20,30);

SELECT * FROMtbl_nameWHEREkey_part1= 10 ANDkey_part2IN (10,20,30);
index
链接类型跟 ALL 同样,不一样的是它只扫描索引树。它一般会比 ALL 快点,由于索引文件一般比数据文件小。MySQL在查询的字段知识单独的索引的一部分的状况下使用这种链接类型。
ALL
将对该表作所有扫描以和从前一个表中取得的记录做联合。这时候若是第一个表没有被标识为 const 的话就不大好了,在其余状况下一般是很是糟糕的。正常地,能够经过增长索引使得能从表中更快的取得记录以免 ALL。
possible_keys
possible_keys 字段是指MySQL在搜索表记录时可能使用哪一个索引。注意,这个字段彻底独立于 EXPLAIN 显示的表顺序。这就意味着 possible_keys 里面所包含的索引可能在实际的使用中没用到。若是这个字段的值是 NULL,就表示没有索引被用到。这种状况下,就能够检查 WHERE 子句中哪些字段那些字段适合增长索引以提升查询的性能。就这样,建立一下索引,而后再用 EXPLAIN 检查一下。想看表都有什么索引,能够经过 SHOW INDEX FROM tbl_name 来看。 key
key 字段显示了MySQL实际上要用的索引。当没有任何索引被用到的时候,这个字段的值就是 NULL。想要让MySQL强行使用或者忽略在 possible_keys 字段中的索引列表,能够在查询语句中使用关键字FORCE INDEX, USE INDEX, 或 IGNORE INDEX。若是是 MyISAM 和 BDB 类型表,可使用 ANALYZE TABLE 来帮助分析使用使用哪一个索引更好。若是是 MyISAM 类型表,运行命令 myisamchk --analyze 也是同样的效果。key_len
key_len 字段显示了MySQL使用索引的长度。当 key 字段的值为 NULL 时,索引的长度就是 NULL。注意,key_len 的值能够告诉你在联合索引中MySQL会真正使用了哪些索引。ref
ref 字段显示了哪些字段或者常量被用来和 key 配合从表中查询记录出来。rows
rows 字段显示了MySQL认为在查询中应该检索的记录数。Extra
本字段显示了查询中MySQL的附加信息。如下是这个字段的几个不一样值的解释:
Distinct
MySQL当找到当前记录的匹配联合结果的第一条记录以后,就再也不搜索其余记录了。Not exists
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 而且用t1.id 的值在 t2 中查找记录。当在 t2 中找到一条匹配的记录时,这就意味着 t2.id 确定不会都是 NULL,就不会再在 t2 中查找相同 id 值的其余记录了。也能够这么说,对于 t1 中的每一个记录,MySQL只须要在 t2 中作一次查找,而无论在 t2 中实际有多少匹配的记录。

range checked for each record (index map: #)
MySQL没找到合适的可用的索引。取代的办法是,对于前一个表的每个行链接,它会作一个检验以决定该使用哪一个索引(若是有的话),而且使用这个索引来从表里取得记录。这个过程不会很快,但总比没有任何索引时作表链接来得快。Using filesort
MySQL须要额外的作一遍从而以排好的顺序取得记录。排序程序根据链接的类型遍历全部的记录,而且将全部符合 WHERE 条件的记录的要排序的键和指向记录的指针存储起来。这些键已经排完序了,对应的记录也会按照排好的顺序取出来。Using index
字段的信息直接从索引树中的信息取得,而再也不去扫描实际的记录。这种策略用于查询时的字段是一个独立索引的一部分。Using temporary
MySQL须要建立临时表存储结果以完成查询。这种状况一般发生在查询时包含了GROUP BY 和 ORDER BY 子句,它以不一样的方式列出了各个字段。Using where
WHERE 子句将用来限制哪些记录匹配了下一个表或者发送给客户端。除非你特别地想要取得或者检查表种的全部记录,不然的话当查询的 Extra 字段值不是 Using where 而且表链接类型是 ALL 或 index 时可能表示有问题。

若是你想要让查询尽量的快,那么就应该注意 Extra 字段的值为Using filesort 和 Using temporary 的状况。

你能够经过 EXPLAIN 的结果中 rows 字段的值的乘积大概地知道本次链接表现如何。它能够粗略地告诉咱们MySQL在查询过程当中会查询多少条记录。若是是使用系统变量max_join_size 来取得查询结果,这个乘积还能够用来肯定会执行哪些多表 SELECT 语句。

下面的例子展现了如何经过 EXPLAIN 提供的信息来较大程度地优化多表联合查询的性能。

假设有下面的 SELECT 语句,正打算用 EXPLAIN 来检测:

EXPLAIN SELECT tt.TicketNumber, tt.TimeIn,
            tt.ProjectReference, tt.EstimatedShipDate,
            tt.ActualShipDate, tt.ClientID,
            tt.ServiceCodes, tt.RepetitiveID,
            tt.CurrentProcess, tt.CurrentDPPerson,
            tt.RecordVolume, tt.DPPrinted, et.COUNTRY,
            et_1.COUNTRY, do.CUSTNAME
        FROM tt, et, et AS et_1, do
        WHERE tt.SubmitTime IS NULL
            AND tt.ActualPC = et.EMPLOYID
            AND tt.AssignedPC = et_1.EMPLOYID
            AND tt.ClientID = do.CUSTNMBR;

在这个例子中,先作如下假设:


  • 要比较的字段定义以下:
    Table Column Column Type
    tt ActualPC CHAR(10)
    tt AssignedPC CHAR(10)
    tt ClientID CHAR(10)
    et EMPLOYID CHAR(15)
    do CUSTNMBR CHAR(15)

  • 数据表的索引以下:
    Table Index
    tt ActualPC
    tt AssignedPC
    tt ClientID
    et EMPLOYID (primary key)
    do CUSTNMBR (primary key)

  • tt.ActualPC 的值是不均匀分布的。

在任何优化措施未采起以前,通过 EXPLAIN  分析的结果显示以下:
table type possible_keys key  key_len ref  rows  Extra
et    ALL  PRIMARY       NULL NULL    NULL 74
do    ALL  PRIMARY       NULL NULL    NULL 2135
et_1  ALL  PRIMARY       NULL NULL    NULL 74
tt    ALL  AssignedPC,   NULL NULL    NULL 3872
           ClientID,
           ActualPC
      range checked for each record (key map: 35)

因为字段 type 的对于每一个表值都是 ALL,这个结果意味着MySQL对全部的表作一个迪卡尔积;这就是说,每条记录的组合。这将须要花很长的时间,由于须要扫描每一个表总记录数乘积的总和。在这状况下,它的积是 74 * 2135 * 74 * 3872 = 45,268,558,720 条记录。若是数据表更大的话,你能够想象一下须要多长的时间。

在这里有个问题是当字段定义同样的时候,MySQL就能够在这些字段上更快的是用索引(对 ISAM 类型的表来讲,除非字段定义彻底同样,不然不会使用索引)。在这个前提下,VARCHAR 和 CHAR是同样的除非它们定义的长度不一致。因为 tt.ActualPC 定义为CHAR(10),et.EMPLOYID 定义为 CHAR(15),两者长度不一致。
为了解决这个问题,须要用 ALTER TABLE 来加大 ActualPC 的长度从10到15个字符:

mysql> ALTER TABLE tt MODIFY ActualPC VARCHAR(15);

如今 tt.ActualPC 和 et.EMPLOYID 都是 VARCHAR(15)
了。再来执行一次 EXPLAIN 语句看看结果:

table type   possible_keys key     key_len ref         rows    Extra
tt    ALL    AssignedPC,   NULL    NULL    NULL        3872    Using
             ClientID,                                         where
             ActualPC
do    ALL    PRIMARY       NULL    NULL    NULL        2135
      range checked for each record (key map: 1)
et_1  ALL    PRIMARY       NULL    NULL    NULL        74
      range checked for each record (key map: 1)
et    eq_ref PRIMARY       PRIMARY 15      tt.ActualPC 1

这还不够,它还能够作的更好:如今 rows 值乘积已经少了74倍。此次查询须要用2秒钟。
第二个改变是消除在比较 tt.AssignedPC = et_1.EMPLOYID 和 tt.ClientID = do.CUSTNMBR 中字段的长度不一致问题:

mysql> ALTER TABLE tt MODIFY AssignedPC VARCHAR(15),
    ->                MODIFY ClientID   VARCHAR(15);

如今 EXPLAIN 的结果以下:

table type   possible_keys key      key_len ref           rows Extra
et    ALL    PRIMARY       NULL     NULL    NULL          74
tt    ref    AssignedPC,   ActualPC 15      et.EMPLOYID   52   Using
             ClientID,                                         where
             ActualPC
et_1  eq_ref PRIMARY       PRIMARY  15      tt.AssignedPC 1
do    eq_ref PRIMARY       PRIMARY  15      tt.ClientID   1

这看起来已是能作的最好的结果了。
遗留下来的问题是,MySQL默认地认为字段tt.ActualPC 的值是均匀分布的,然而表tt 并不是如此。幸亏,咱们能够很方便的让MySQL分析索引的分布:

mysql>ANALYZE TABLE tt;

到此为止,表链接已经优化的很完美了,EXPLAIN 的结果以下:

table type   possible_keys key     key_len ref           rows Extra
tt    ALL    AssignedPC    NULL    NULL    NULL          3872 Using
             ClientID,                                        where
             ActualPC
et    eq_ref PRIMARY       PRIMARY 15      tt.ActualPC   1
et_1  eq_ref PRIMARY       PRIMARY 15      tt.AssignedPC 1
do    eq_ref PRIMARY       PRIMARY 15      tt.ClientID   1

请注意,EXPLAIN 结果中的 rows 字段的值也是MySQL的链接优化程序大体猜想的,请检查这个值跟真实值是否基本一致。若是不是,能够经过在 SELECT 语句中使用STRAIGHT_JOIN 来取得更好的性能,同时能够试着在 FROM 分句中用不一样的次序列出各个表。

相关文章
相关标签/搜索