EXPLAINtbl_name
或者: mysql
EXPLAIN SELECTselect_options
EXPLAIN 语句能够被看成 DESCRIBE 的同义词来用,也能够用来获取一个MySQL要执行的 SELECT 语句的相关信息。 sql
本章节主要讲述了第二种 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 结果的每行记录显示了每一个表的相关信息,每行记录都包含如下几个字段: 排序
idSELECT * FROMtbl_nameWHEREprimary_key=1; SELECT * FROMtbl_nameWHEREprimary_key_part1=1 ANDprimary_key_part2=2;eq_ref
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
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
SELECT * FROMref_tableWHEREkey_column=exprORkey_columnIS NULL;
valueIN (SELECTprimary_keyFROMsingle_tableWHEREsome_expr)
unique_subquery 只是用来彻底替换子查询的索引查找函数效率更高了。 索引
index_subqueryvalueIN (SELECTkey_columnFROMsingle_tableWHEREsome_expr)range
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
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: #)你能够经过 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) |
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 分句中用不一样的次序列出各个表。