|
在分析查询性能时,考虑EXPLAIN关键字同样很管用。EXPLAIN关键字一般放在SELECT查询语句的前面,用于描述MySQL如何执行查询操作、以及MySQL成功返回结果集需要执行的行数。explain 可以帮助我们分析 select 语句,让我们知道查询效率低下的原因,从而改进我们查询,让查询优化器能够更好的工作。
一、MySQL 查询优化器是如何工作的
MySQL 查询优化器有几个目标,但是其中最主要的目标是尽可能地使用索引,并且使用最严格的索引来消除尽可能多的数据行。最终目标是提交 SELECT 语句查找数据行,而不是排除数据行。优化器试图排除数据行的原因在于它排除数据行的速度越快,那么找到与条件匹配的数据行也就越快。如果能够首先进行最严格的测试,查询就可以执行地更快。
EXPLAIN 的每个输出行提供一个表的相关信息,并且每个行包括下面的列:
项
说明
id
MySQL Query Optimizer 选定的执行计划中查询的序列号。表示查询中执行 select 子句或操作表的顺序,id 值越大优先级越高,越先被执行。id 相同,执行顺序由上至下。
select_type 查询类型
说明
SIMPLE
简单的 select 查询,不使用 union 及子查询
PRIMARY
最外层的 select 查询
UNION
UNION 中的第二个或随后的 select 查询,不依赖于外部查询的结果集
DEPENDENT UNION
UNION 中的第二个或随后的 select 查询,依赖于外部查询的结果集
SUBQUERY
子查询中的第一个 select 查询,不依赖于外部查询的结果集
DEPENDENT SUBQUERY
子查询中的第一个 select 查询,依赖于外部查询的结果集
DERIVED
用于 from 子句里有子查询的情况。 MySQL 会递归执行这些子查询, 把结果放在临时表里。
UNCACHEABLE SUBQUERY
结果集不能被缓存的子查询,必须重新为外层查询的每一行进行评估。
UNCACHEABLE UNION
UNION 中的第二个或随后的 select 查询,属于不可缓存的子查询
项
说明
table
输出行所引用的表
type 重要的项,显示连接使用的类型,按最优到最差的类型排序
说明
system
表仅有一行(=系统表)。这是 const 连接类型的一个特例。
const
const 用于用常数值比较 PRIMARY KEY 时。当查询的表仅有一行时,使用 System。
eq_ref
const 用于用常数值比较 PRIMARY KEY 时。当查询的表仅有一行时,使用 System。
ref
连接不能基于关键字选择单个行,可能查找到多个符合条件的行。叫做 ref 是因为索引要跟某个参考值相比较。这个参考值或者是一个常数,或者是来自一个表里的多表查询的结果值。
ref_or_null
如同 ref, 但是 MySQL 必须在初次查找的结果里找出 null 条目,然后进行二次查找。
index_merge
说明索引合并优化被使用了。
unique_subquery
在某些 IN 查询中使用此种类型,而不是常规的 ref:value IN (SELECT primary_key FROM single_table WHERE some_expr)
index_subquery
在某些 IN 查询中使用此种类型 , 与 unique_subquery 类似,但是查询的是非唯一性索引: value IN (SELECT key_column FROM single_table WHERE some_expr)
range
只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引。当使用=、 、>、>=、 select_type: SIMPLE
table: article
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 3
Extra: Using where; Using filesort
1 row in set (0.00 sec)
很显然,type 是 ALL,即最坏的情况。Extra 里还出现了 Using filesort,也是最坏的情况。优化是必须的。
嗯,那么最简单的解决方案就是加索引了。好,我们来试一试。查询的条件里即 where 之后共使用了category_id,comments,views 三个字段。那么来一个联合索引是最简单的了。
复制代码代码如下:
ALTER TABLE `article` ADD INDEX x ( `category_id` , `comments`, `views` );
结果有了一定好转,但仍然很糟糕:
复制代码代码如下:
*************************** 1. row ***************************
> select_type: SIMPLE
table: article
type: range
possible_keys: x
key: x
key_len: 8
ref: NULL
rows: 1
Extra: Using where; Using filesort
1 row in set (0.00 sec)
type 变成了range,这是可以忍受的。但是 extra 里使用 Using filesort 仍是无法接受的。但是我们已经建立了索引,为啥没用呢?这是因为按照 BTree 索引的工作原理,先排序 category_id,如果遇到相同的 category_id 则再排序 comments,如果遇到相同的 comments 则再排序 views。当 comments 字段在联合索引里处于中间位置时,因comments > 1 条件是一个范围值(所谓 range),MySQL 无法利用索引再对后面的 views 部分进行检索,即 range 类型查询字段后面的索引无效。
那么我们需要抛弃 comments,删除旧索引:
复制代码代码如下:
DROP INDEX x ON article;
然后建立新索引:
复制代码代码如下:
ALTER TABLE `article` ADD INDEX y ( `category_id` , `views` ) ;
接着再运行查询:
复制代码代码如下:
*************************** 1. row ***************************
> select_type: SIMPLE
table: article
type: ref
possible_keys: y
key: y
key_len: 4
ref: const
rows: 1
Extra: Using where
1 row in set (0.00 sec)
可以看到,type 变为了 ref,Extra 中的 Using filesort 也消失了,结果非常理想。
再来看一个多表查询的例子。
首先定义 3个表> 复制代码代码如下:
CREATE TABLE IF NOT EXISTS `class` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`card` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE IF NOT EXISTS `book` (
`bookid` int(10) unsigned NOT NULL AUTO_INCREMENT,
`card` int(10) unsigned NOT NULL,
PRIMARY KEY (`bookid`)
);
CREATE TABLE IF NOT EXISTS `phone` (
`phoneid` int(10) unsigned NOT NULL AUTO_INCREMENT,
`card` int(10) unsigned NOT NULL,
PRIMARY KEY (`phoneid`)
) engine = innodb;
然后再分别插入大量数据。插入数据的php脚本:
复制代码代码如下:
|
|