MySQL优化—工欲善其事,必先利其器之EXPLAIN

最近慢慢接触MySQL,了解如何优化它也迫在眉睫了,话说工欲善其事,必先利其器。最近我就打算了解下几个优化MySQL中常常用到的工具。今天就简单介绍下EXPLAIN。html

内容导航

 

环境准备

MySQL版本:

建立测试表

CREATE TABLE people(
    id bigint auto_increment primary key,
    zipcode char(32) not null default '',
    address varchar(128) not null default '',
    lastname char(64) not null default '',
    firstname char(64) not null default '',
    birthdate char(10) not null default ''
);

CREATE TABLE people_car(
    people_id bigint,
    plate_number varchar(16) not null default '',
    engine_number varchar(16) not null default '',
    lasttime timestamp
);

插入测试数据

insert into people
(zipcode,address,lastname,firstname,birthdate)
values
('230031','anhui','zhan','jindong','1989-09-15'),
('100000','beijing','zhang','san','1987-03-11'),
('200000','shanghai','wang','wu','1988-08-25')

insert into people_car
(people_id,plate_number,engine_number,lasttime)
values
(1,'A121311','12121313','2013-11-23 :21:12:21'),
(2,'B121311','1S121313','2011-11-23 :21:12:21'),
(3,'C121311','1211SAS1','2012-11-23 :21:12:21')

建立索引用来测试

alter table people add key(zipcode,firstname,lastname);

 

EXPLAIN 介绍

 先从一个最简单的查询开始:mysql

Query-1
explain select zipcode,firstname,lastname from people;

EXPLAIN输出结果共有id,select_type,table,type,possible_keys,key,key_len,ref,rows和Extra几列。sql

id

Query-2
explain select zipcode from (select * from people a) b;

id是用来顺序标识整个查询中SELELCT 语句的,经过上面这个简单的嵌套查询能够看到id越大的语句越先执行。该值可能为NULL,若是这一行用来讲明的是其余行的联合结果,好比UNION语句:服务器

Query-3
explain select * from people where zipcode = 100000 union select * from people where zipcode = 200000;

 

select_type

SELECT语句的类型,能够有下面几种。函数

SIMPLE工具

最简单的SELECT查询,没有使用UNION或子查询。见Query-1性能

 

PRIMARY测试

在嵌套的查询中是最外层的SELECT语句,在UNION查询中是最前面的SELECT语句。见Query-2Query-3优化

 

UNIONui

UNION中第二个以及后面的SELECT语句。 见Query-3

 

DERIVED

派生表SELECT语句中FROM子句中的SELECT语句。见Query-2

 

UNION RESULT

一个UNION查询的结果。见Query-3

 

DEPENDENT UNION

顾名思义,首先须要知足UNION的条件,及UNION中第二个以及后面的SELECT语句,同时该语句依赖外部的查询。

Query-4
explain select * from people where id in  (select id from people where zipcode = 100000 union select id from people where zipcode = 200000 );

Query-4中select id from people where zipcode = 200000的select_type为DEPENDENT UNION。你也许很奇怪这条语句并无依赖外部的查询啊。

这里顺带说下MySQL优化器对IN操做符的优化,优化器会将IN中的uncorrelated subquery优化成一个correlated subquery(关于correlated subquery参见这里)。

SELECT ... FROM t1 WHERE t1.a IN (SELECT b FROM t2);

相似这样的语句会被重写成这样:

SELECT ... FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.b = t1.a);

因此Query-4实际上被重写成这样:

Query-5
explain select * from people o where exists  (select id from people where zipcode = 100000 and id = o.id union select id from people where zipcode = 200000  and id = o.id);

题外话:有时候MySQL优化器这种太过“聪明” 的作法会致使WHERE条件包含IN()的子查询语句性能有很大损失。能够参看《高性能MySQL第三版》6.5.1关联子查询一节

 

SUBQUERY

子查询中第一个SELECT语句。

Query-6
explain select * from people  where id =  (select id from people where zipcode = 100000);

 

DEPENDENT SUBQUERY

和DEPENDENT UNION相对UNION同样。见Query-5

除了上述几种常见的select_type以外还有一些其余的这里就不一一介绍了,不一样MySQL版本也不尽相同。

 

table

显示的这一行信息是关于哪一张表的。有时候并非真正的表名。

Query-7
explain select * from (select * from (select * from people a) b ) c;

能够看到若是指定了别名就显示的别名。

<derivedN>N就是id值,指该id值对应的那一步操做的结果。

还有<unionM,N>这种类型,出如今UNION语句中,见Query-4

注意:MySQL对待这些表和普通表同样,可是这些“临时表”是没有任何索引的。

 

type

type列很重要,是用来讲明表与表之间是如何进行关联操做的,有没有使用索引。MySQL中“关联”一词比通常意义上的要宽泛,MySQL认为任何一次查询都是一次“关联”,并不只仅是一个查询须要两张表才叫关联,因此也能够理解MySQL是如何访问表的。主要有下面几种类别。

const

当肯定最多只会有一行匹配的时候,MySQL优化器会在查询前读取它并且只读取一次,所以很是快。const只会用在将常量和主键或惟一索引进行比较时,并且是比较全部的索引字段。people表在id上有一个主键索引,在(zipcode,firstname,lastname)有一个二级索引。所以Query-8的type是const而Query-9并非:

Query-8
explain select * from people where id=1;

Query-9
explain select * from people where zipcode = 100000;

注意下面的Query-10也不能使用const table,虽然也是主键,也只会返回一条结果。

Query-10
explain select * from people where id >2;

 

system

这是const链接类型的一种特例,表仅有一行知足条件。

Query-11
explain select * from (select * from people where id = 1 )b;

<derived2>已是一个const table而且只有一条记录。

 

eq_ref

eq_ref类型是除了const外最好的链接类型,它用在一个索引的全部部分被联接使用而且索引是UNIQUE或PRIMARY KEY。

须要注意InnoDB和MyISAM引擎在这一点上有点差异。InnoDB当数据量比较小的状况type会是All。咱们上面建立的people 和 people_car默认都是InnoDB表。

Query-12
explain select * from people a,people_car b where a.id = b.people_id;

咱们建立两个MyISAM表people2和people_car2试试:

CREATE TABLE people2(
    id bigint auto_increment primary key,
    zipcode char(32) not null default '',
    address varchar(128) not null default '',
    lastname char(64) not null default '',
    firstname char(64) not null default '',
    birthdate char(10) not null default ''
)ENGINE = MyISAM;

CREATE TABLE people_car2(
    people_id bigint,
    plate_number varchar(16) not null default '',
    engine_number varchar(16) not null default '',
    lasttime timestamp
)ENGINE = MyISAM;
Query-13
explain select * from people2 a,people_car2 b where a.id = b.people_id;

我想这是InnoDB对性能权衡的一个结果。

eq_ref能够用于使用 = 操做符比较的带索引的列。比较值能够为常量或一个使用在该表前面所读取的表的列的表达式。若是关联所用的索引恰好又是主键,那么就会变成更优的const了:

Query-14
explain select * from people2 a,people_car2 b where a.id = b.people_id and b.people_id = 1;

 

ref

这个类型跟eq_ref不一样的是,它用在关联操做只使用了索引的最左前缀,或者索引不是UNIQUE和PRIMARY KEY。ref能够用于使用=或<=>操做符的带索引的列。

为了说明咱们从新创建上面的people2和people_car2表,仍然使用MyISAM可是不给id指定primary key。而后咱们分别给id和people_id创建非惟一索引。

reate index people_id on people2(id);
create index people_id on people_car2(people_id);

而后再执行下面的查询:

Query-15
explain select * from people2 a,people_car2 b where a.id = b.people_id and a.id > 2;

Query-16
explain select * from people2 a,people_car2 b where a.id = b.people_id and a.id = 2;

Query-17
explain select * from people2 a,people_car2 b where a.id = b.people_id;

Query-18
explain select * from people2 where id = 1;

看上面的Query-15,Query-16和Query-17,Query-18咱们发现MyISAM在ref类型上的处理也是有不一样策略的。

对于ref类型,在InnoDB上面执行上面三条语句结果彻底一致。

 

fulltext

连接是使用全文索引进行的。通常咱们用到的索引都是B树,这里就不举例说明了。

 

ref_or_null

该类型和ref相似。可是MySQL会作一个额外的搜索包含NULL列的操做。在解决子查询中常用该联接类型的优化。(详见这里)。

Query-19
mysql> explain select * from people2 where id = 2 or id is null;

Query-20
explain select * from people2 where id = 2 or id is not null;

注意Query-20使用的并非ref_or_null,并且InnnoDB此次表现又不相同(数据量大的状况下有待验证)。

 

index_merger

该联接类型表示使用了索引合并优化方法。在这种状况下,key列包含了使用的索引的清单,key_len包含了使用的索引的最长的关键元素。关于索引合并优化看这里

 

unique_subquery

该类型替换了下面形式的IN子查询的ref:

value IN (SELECT primary_key FROM single_table WHERE some_expr)

unique_subquery是一个索引查找函数,能够彻底替换子查询,效率更高。

 

index_subquery

该联接类型相似于unique_subquery。能够替换IN子查询,但只适合下列形式的子查询中的非惟一索引:

value IN (SELECT key_column FROM single_table WHERE some_expr)

 

range

只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪一个索引。key_len包含所使用索引的最长关键元素。在该类型中ref列为NULL。当使用=、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN或者IN操做符,用常量比较关键字列时,可使用range:

Query-21
explain select * from people where id = 1 or id = 2;


注意在个人测试中:发现只有id是主键或惟一索引时type才会为range。

这里顺便挑剔下MySQL使用相同的range来表示范围查询和列表查询。

explain select * from people where id >1;

 explain select * from people where id in (1,2);

但事实上这两种状况下MySQL如何使用索引是有很大差异的:

咱们不是挑剔:这两种访问效率是不一样的。对于范围条件查询,MySQL没法使用范围列后面的其余索引列了,可是对于“多个等值条件查询”则没有这个限制了。

——出自《高性能MySQL第三版》

 

index

该联接类型与ALL相同,除了只有索引树被扫描。这一般比ALL快,由于索引文件一般比数据文件小。这个类型一般的做用是告诉咱们查询是否使用索引进行排序操做。

Query-22
explain select * from people order by id;

至于什么状况下MySQL会利用索引进行排序,等有时间再仔细研究。最典型的就是order by后面跟的是主键。

 

ALL

最慢的一种方式,即全表扫描。

 

总的来讲:上面几种链接类型的性能是依次递减的(system>const),不一样的MySQL版本、不一样的存储引擎甚至不一样的数据量表现均可能不同。

 

possible_keys

possible_keys列指出MySQL能使用哪一个索引在该表中找到行。

 

key

key列显示MySQL实际决定使用的键(索引)。若是没有选择索引,键是NULL。要想强制MySQL使用或忽视possible_keys列中的索引,在查询中使用FORCE INDEX、USE INDEX或者IGNORE INDEX。

 

key_len

key_len列显示MySQL决定使用的键长度。若是键是NULL,则长度为NULL。使用的索引的长度。在不损失精确性的状况下,长度越短越好 。

 

ref

ref列显示使用哪一个列或常数与key一块儿从表中选择行。

 

rows

rows列显示MySQL认为它执行查询时必须检查的行数。注意这是一个预估值。

 

Extra

Extra是EXPLAIN输出中另一个很重要的列,该列显示MySQL在查询过程当中的一些详细信息,包含的信息不少,只选择几个重点的介绍下。

Using filesort 

MySQL有两种方式能够生成有序的结果,经过排序操做或者使用索引,当Extra中出现了Using filesort 说明MySQL使用了后者,但注意虽然叫filesort但并非说明就是用了文件来进行排序,只要可能排序都是在内存里完成的。大部分状况下利用索引排序更快,因此通常这时也要考虑优化查询了。

 

Using temporary

说明使用了临时表,通常看到它说明查询须要优化了,就算避免不了临时表的使用也要尽可能避免硬盘临时表的使用。

 

Not exists

MYSQL优化了LEFT JOIN,一旦它找到了匹配LEFT JOIN标准的行, 就再也不搜索了。

 

Using index 

说明查询是覆盖了索引的,这是好事情。MySQL直接从索引中过滤不须要的记录并返回命中的结果。这是MySQL服务层完成的,但无需再回表查询记录。

 

Using index condition

这是MySQL 5.6出来的新特性,叫作“索引条件推送”。简单说一点就是MySQL原来在索引上是不能执行如like这样的操做的,可是如今能够了,这样减小了没必要要的IO操做,可是只能用在二级索引上,详情点这里

 

Using where

使用了WHERE从句来限制哪些行将与下一张表匹配或者是返回给用户。

注意:Extra列出现Using where表示MySQL服务器将存储引擎返回服务层之后再应用WHERE条件过滤。

 

EXPLAIN的输出内容基本介绍完了,它还有一个扩展的命令叫作EXPLAIN EXTENDED,主要是结合SHOW WARNINGS命令能够看到一些更多的信息。一个比较有用的是能够看到MySQL优化器重构后的SQL。

 

Ok,EXPLAIN了解就到这里,其实这些内容网上都有,只是本身实际操练下会印象更深入。下一节会介绍SHOW PROFILE、慢查询日志以及一些第三方工具。

相关文章
相关标签/搜索