高性能MySQL(EXPLAIN 附录D)

调用EXPLAIN

要使用EXPLAIN,只需要在查询的select关键字前面机上explain这个词。下面是一个简单的explain结果:

mysql> explain select 1\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: NULL
         type: NULL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: NULL
        Extra: No tables used
1 row in set (0.00 sec)

EXPLAIN有两个主要变种。EXPLAIN EXTENDED,EXPLAIN PARTITIONS。EXPLAIN 时 ,SELECT语句也会实际执行。EXPLAIN只是个近似结果,以下是一些相关限制。

  • EXPLAIN不会显示触发器、存储过程、UDF的性能。
  • 不会显示一个MySQL的内置优化
  • 不区分具有相同名字的事物。例如内存排序和临时文件排序都使用"filesort",对于磁盘和内存中的临时表都表示"Using temporary"

EXPLAIN中的列

  • id列

编号标识SELECT所属的行。若语句中没有子查询或联合那么id应该是相同的1。否则内层的select语句一般会顺序编号,对应其在原始语句的位置。MySQL将SELECT分为简单和复杂两种,复杂查询主要三大类:简单子查询、from子句中的子查询、UNION查询。下面是一个简单的子查询,

#简单子查询
mysql> explain select (select 1 from actor limit 1) from film;
+----+-------------+-------+-------+---------------+---------------------+---------+------+------+-------------+
| id | select_type | table | type  | possible_keys | key                 | key_len | ref  | rows | Extra       |
+----+-------------+-------+-------+---------------+---------------------+---------+------+------+-------------+
|  1 | PRIMARY     | film  | index | NULL          | idx_fk_language_id  | 1       | NULL | 1000 | Using index |
|  2 | SUBQUERY    | actor | index | NULL          | idx_actor_last_name | 137     | NULL |  200 | Using index |
+----+-------------+-------+-------+---------------+---------------------+---------+------+------+-------------+

#from子句子查询 这是有一个der临时表
mysql> explain select film_id from (select film_id from film) AS der;
+----+-------------+------------+-------+---------------+--------------------+---------+------+------+-------------+
| id | select_type | table      | type  | possible_keys | key                | key_len | ref  | rows | Extra       |
+----+-------------+------------+-------+---------------+--------------------+---------+------+------+-------------+
|  1 | PRIMARY     | <derived2> | ALL   | NULL          | NULL               | NULL    | NULL | 1000 | NULL        |
|  2 | DERIVED     | film       | index | NULL          | idx_fk_language_id | 1       | NULL | 1000 | Using index |
+----+-------------+------------+-------+---------------+--------------------+---------+------+------+-------------+

#union查询的结果总是放到一个临时表里,因为不在原生SQL中,所以id列为null
mysql> explain select 1 union all select 1;
+----+--------------+------------+------+---------------+------+---------+------+------+-----------------+
| id | select_type  | table      | type | possible_keys | key  | key_len | ref  | rows | Extra           |
+----+--------------+------------+------+---------------+------+---------+------+------+-----------------+
|  1 | PRIMARY      | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL | No tables used  |
|  2 | UNION        | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL | No tables used  |
| NULL | UNION RESULT | <union1,2> | ALL  | NULL          | NULL | NULL    | NULL | NULL | Using temporary |
+----+--------------+------------+------+---------------+------+---------+------+------+-----------------+
  • select_type

这一列显示了对应航是简单还是复杂SELECT。SIMPLE 指简单查询,不熬扩子查询和UNION。如果包含复杂查询,则最外层显示PRIMARY,内层部分可能显示如下:
SUBQUERY 包含在select 查询列中的子查询。
DERIVED 包含在from子句的子查询,MySQL会递归执行并将结果放到一个临时表中。
UNION 在UNION中的第二个和随后的select被标记为union。如果union被from 子句中的子查询包含,那第一个select会被标记为DERIVED。
UNION RESULT 用来标记UNION的匿名临时表检索结果的select。

  • table 列

这一列显示了对应航正在访问表的表名或别名。如下看一看到这条SQL的查询顺序,先关联actor&film_actor,再关联actor

mysql> explain select film.film_id from film inner join film_actor using(film_id) inner join actor using(actor_id);
+----+-------------+------------+--------+------------------------+---------------------+---------+---------------------------+------+-------------+
| id | select_type | table      | type   | possible_keys          | key                 | key_len | ref                       | rows | Extra       |
+----+-------------+------------+--------+------------------------+---------------------+---------+---------------------------+------+-------------+
|  1 | SIMPLE      | actor      | index  | PRIMARY                | idx_actor_last_name | 137     | NULL                      |  200 | Using index |
|  1 | SIMPLE      | film_actor | ref    | PRIMARY,idx_fk_film_id | PRIMARY             | 2       | sakila.actor.actor_id     |   13 | Using index |
|  1 | SIMPLE      | film       | eq_ref | PRIMARY                | PRIMARY             | 2       | sakila.film_actor.film_id |    1 | Using index |
+----+-------------+------------+--------+------------------------+---------------------+---------+---------------------------+------+-------------+

派生表和联合。FROM子句有子查询时,table列时<derivedN>的形式,其中N是子查询的id,指向下一个查询的行。
当有UNION时,UNION RESULT的table包含所有参与UNION的查询行id。下面我们来看一个复杂的查询例子,:

mysql> explain select actor_id, 
(select 1 from film_actor where film_actor.actor_id = der_1.actor_id limit 1) 
from  
(select actor_id from actor limit 5) as der_1 
union all select film_id, 
(select @var1 from rental limit 1) 
from 
(select film_id,
(select 1 from store limit 1) from film limit 5) as der_2;
+----+----------------------+------------+-------+---------------+---------------------+---------+----------------+-------+-----------------+
| id | select_type          | table      | type  | possible_keys | key                 | key_len | ref            | rows  | Extra           |
+----+----------------------+------------+-------+---------------+---------------------+---------+----------------+-------+-----------------+
|  1 | PRIMARY              | <derived3> | ALL   | NULL          | NULL                | NULL    | NULL           |     5 | NULL            |
|  3 | DERIVED              | actor      | index | NULL          | idx_actor_last_name | 137     | NULL           |   200 | Using index     |
|  2 | DEPENDENT SUBQUERY   | film_actor | ref   | PRIMARY       | PRIMARY             | 2       | der_1.actor_id |    13 | Using index     |
|  4 | UNION                | <derived6> | ALL   | NULL          | NULL                | NULL    | NULL           |     5 | NULL            |
|  6 | DERIVED              | film       | index | NULL          | idx_fk_language_id  | 1       | NULL           |  1000 | Using index     |
|  7 | SUBQUERY             | store      | index | NULL          | idx_unique_manager  | 1       | NULL           |     2 | Using index     |
|  5 | UNCACHEABLE SUBQUERY | rental     | index | NULL          | idx_fk_staff_id     | 1       | NULL           | 16008 | Using index     |
| NULL | UNION RESULT         | <union1,4> | ALL   | NULL          | NULL                | NULL    | NULL           |  NULL | Using temporary |
+----+----------------------+------------+-------+---------------+---------------------+---------+----------------+-------+-----------------+

#从上往下看,第一行id=1 table=<derived3> 指向id=3 的行建立的临时表,即原SQL 第一行从临时表der_1查询部分;
#第二行id=3 ,嵌套在from 子句中的子查询派生表,所以select_tyoe=DERIVED, 即原SQL 第四行 建立der_1临时表这部分 ;
#第三行id=2,DEPENDENT SUBQUERY依赖子查询,这里即原SQL中第二行依赖der_1即id=3的子查询派生表,所以她在id=3更高的行后面,暗示了在第二行后面执行;
#第四行id=4为UNION,意味着是原生SQL中UNION 后的第二个或之后的select,即原SQL第六行检索@var1这部分,table=<derived6>指向id=6的DRIVERD行;
#第五行id=6在原生SQL中第九行,定义der_2临时表的派生表子查询;
#第六行id=7 是一个简单子查询SUBQUERY,原生SQL定义der_2的select store 部分,另外因为下面一行id=5,所以id=6的DERIVED嵌套子查询到这一行结束;
#第七行id=5即原生SQL有用户变量@var1部分,这里已经不是派生表嵌套子查询部分,而逝检索派生表部分;
#最后一行UNION RESULT表明了从临时表中读取行的,这里1 ,4说明是读取id=1,4的行的临时表即<derived3>、<derived6> 
  • type列

访问类型,换言之就是MySQL如何查找表中的行。
ALL: 俗称的全表扫描,通常意味着MySQL必须扫描整张表,去找到需要的行。如果查询中使用了LIMIT关键字,或者在Extra列中显示"Using distinct/not exists"。
index: 全索引扫描,跟全表扫描一样,不过是按索引次序进行而不是行顺序。优点是避免了排序,缺点是要按照索引次序回表读取的开销,因为此时回表很可能是随机访问,开销非常大。不过如果Extra列显示的是Using index,说明使用了覆盖索引,只扫描索引的数据,没有回表操作,开销要大大减少。
range: 范围扫描是一个有限制的索引扫描它开始于索引里的某一点,返回匹配这个值域的行,这笔全索引扫描好一些,因为用不着全部遍历,显然是带有BETWEEN或WHERE 子句带>的查询。MySQL中使用索引去查一系列值时,IN()或OR列表,也会显示为范围查询,但这两者其实是不同的访问类型。
ref: 索引访问,它返回所有匹配某个单个值的行,可能会找到多个符合条件的行。此类索引访问只有使用非唯一索引或者唯一性索引的非唯一性前缀时才会发生。取名ref因为索引要跟某个参考值作比较,这个参考值或者是一个常数或者是来自多表查询里一个表的结果值。ref_or_null是ref的一个变体,它表示MySQL必须在初次查找的结果中查找出NULL条目。
eq_ref: 对多只返回一条记录的索引查找。在使用主键或者唯一性索引查找时看到,它会将索引与某个参考值作比较。
const, system: 当MySQL能对查询的某部分进行优化并转换成一个常量时,就会使用这些访问类型。如将主键置于where列表中,MySQL就能将该查询转换为一个常量,system是const类型的特例,当查询的表只有一行的情况下,使用system。
NULL: 这种访问方式意味着MySQL能在优化阶段分解语句,在执行阶段甚至用不着在访问表或者索引。例如,从一个索引列里选取最小值可以单独通过查找索引来完成。

  • possible_keys

这一列显示了查询可以使用那些索引。

  • key列

这一列显示了MySQL决定采用哪个索引来优化对该表的访问。如果该索引没有出现在poosible_keys中,那么选用它是出于另外的原因,可能是选择了覆盖索引。
换句话说,possible_keys揭示了哪一个索引能有助于高效的行查找,key显示的是优化采用什么索引减少查询成本。

  • key_len列

该列显示了MySQL在索引里使用的的字节数。

  • ref列

这一列显示了之前的表在key列记录的索引中查找值所用的列或常量。

  • rows列

这一列是MySQL为了找到所需的行而要读取的行数,注意并不是结果集里的行数。

  • filtered列

显示的是针对表里符合某个调价你的记录数的百分比悲观估算。

  • Extra

"Using index" 表示MySQL将使用覆盖索引,以避免表访问。
"Using where" 表示MySQL服务器将在存储引擎检索行后在进行过滤。有些WHERE条件里涉及索引当中的列,存储引擎读取索引时就可以进行校验,此时便不会显示Using Where。
"Using temporary" 这意味MySQL在对结果排序时会使用一个临时表。
"Using filesort" 这意味着MySQL会对结果使用一个外部索引排序,而不是按照索引次序。
"Range checked for each record(index map:N)" 这个值意味着没有好用的索引,新的索引在连接的每一行上重新估算。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容