zoukankan      html  css  js  c++  java
  • MySQL的EXPLAIN调试

      这里显示了如何调用“EXPLAIN”来获取关于査询执行计划的信息,以及如何解释输出。EXPLAIN命令是查看査询优化器如何决定执行査询的主要方法。这个功能有局限性,并不总会说出真相,但它的输出是可以获取的最好信息,值得花时间了解,因为可以学习到査询是如何执行的。学会解释EXPLAIN将帮助你了解MySQL优化器是如何工作的。

     

    1.调用EXPLAIN

      要使用EXPLAIN,只需在査询中的SELECT关键字之前增加EXPLAIN这个词。MySQL会在査询上设置一个标记。当执行査询时,这个标记会使其返回关于在执行计划中每一步的信息,而不是执行它。它会返回一行或多行信息,显示出执行计划中的每一部分和执行的次序。

      下面是一个可能的最简单的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

      在査询中每个表在输出中只有一行。如果査询是两个表的联接,那么输出中将有两行。别名表单算为一个表,因此,如果把一个表与自己联接,输出中也会有两行。“表”的意义在这里相当广:可以是一个子査询,一个UNION结果,等等。稍后会看到为什么是这样。

      EXPLAIN有两个主要的变种。

    • EXPLAIN EXTENDED看起来和正常的EXPLAIN的行为一样,但它会告诉服务器“逆向编译”执行计划为一个SELECT语句。可以通过紧接其后运行SHOW WARNINGS看到这个生成的语句。这个语句直接来自执行计划,而不是原SQL语句,到这点上已经变成一个数据结构。在大部分场景下它都与原语句不相同。你可以检测査询优化器到底是如何转化语句的。EXPLAIN EXTENDED在MySQL5.0和更新版本中可用,在MySQL5.1 (稍后会做更多讨论)额外增加了一个filtered列。
    • EXPLAIN PARTITIONS会显示査询将访问的分区,如果査询是基于分区表的话。它只在MySQL5.1和更新版本中存在。

      认为增加EXPLAIN时MySQL不会执行査询,这是一个常见的错误。事实上,如果査询在FROM子句中包括子査询,那么MySQL实际上会执行子査询,将其结果放在一个临时表中,然后完成外层査询优化。它必须在可以完成外层査询优化之前处理所有类似的子査询,这对于EXPLAIN来说是必须要做的。这意味着如果语句包含开销较大的子査询或使用临时表算法的视图,实际上会给服务器带来大量工作。

      要意识到EXPLAIN只是个近似结果,别无其他。有时候它是一个很好的近似,但在其他时候,可能与真相相差甚远。以下是一些相关的限制。

    • EXPLAIN根本不会告诉你触发器、存储过程或UDF会如何影响査询。
    • 它并不支持存储过程,尽管可以手动抽取査询并单独地对其进行EXPLAIN操作。
    • 它并不会告诉你MySQL在査询执行中所做的特定优化。
    • 它并不会显示关于査询的执行计划的所有信息(MySQL开发者会尽可能增加更多信息)。
    • 它并不区分具有相同名字的事物。例如,它对内存排序和临时文件都使用“filesort”,并且对于磁盘上和内存中的临时表都显示“Using temporary”。
    • 可能会误导。例如,它会对一个有着很小LIMIT的査询显示全索引扫描。(MySQL5.1的EXPLAIN关于检査的行数会显示更精确的信息,但早期版本并不考虑LIMIT。)

     

    1.1 重写非SELECT查询

      MySQL EXPLAIN只能解释SELECT査询,并不会对存储程序调用和INSERT、UPDATE、DELETE或其他语句做解释。然而,你可以重写某些非SELECT査询以利用EXPLAIN。为了达到这个目的,只需要将该语句转化成一个等价的访问所有相同列的SELECT。任何提及的列都必须在SELECT列表,关联子句,或者WHERE子句中。

      例如,假如你想重写下面的UPDATE语句以使其可以利用EXPLAIN。

    UPDATE sakila.actor
       INNER JOIN sakila.film_actor USING (actor_id)
    SET actor.last_update=film_actor.last_update;

      下面的EXPLAIN语句并不等价于上面的UPDATE,因为它并不要求服务器从任何一个表上获取last update列。

    mysql> EXPLAIN SELECT film_actor.actor_id
        -> FROM sakila.actor
        ->    INNER JOIN sakila.film_actor USING (actor_id)\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: actor
             type: index
    possible_keys: PRIMARY
              key: PRIMARY
          key_len: 2
              ref: NULL
             rows: 200
            Extra: Using index
    *************************** 2. row ***************************
               id: 1
      select_type: SIMPLE
            table: film_actor
             type: ref
    possible_keys: PRIMARY
              key: PRIMARY
          key_len: 2
              ref: sakila.actor.actor_id
             rows: 13
            Extra: Using index

      这个差别非常重要。例如,输出结果显示MySQL将使用覆盖索引,但是,当检索并更新last_updated列时,就无法使用覆盖索引了。下面这种改写法就更接近原来的语句:

    mysql> EXPLAIN SELECT film_actor.last_update, actor.last_update
        -> FROM sakila.actor
        ->    INNER JOIN sakila.film_actor USING (actor_id)\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: actor
             type: ALL
    possible_keys: PRIMARY
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 200
            Extra:
    *************************** 2. row ***************************
               id: 1
      select_type: SIMPLE
            table: film_actor
             type: ref
    possible_keys: PRIMARY
              key: PRIMARY
          key_len: 2
              ref: sakila.actor.actor_id
             rows: 13
            Extra:

      像这样重写查询并不非常科学,但对帮助理解査询是怎么做的经常已足够好了。

      显示计划时,对于写査询并没有“等价”的读査询,理解这一点非常重要。一个SELECT查询只需要找到数据的一份副本并返回。而任何修改数据的査询必须在所有索引上査找并修改其所有副本。这常常比看起来等价的SELECT査询的消耗要髙得多。

     

    2.EXPLAIN中的列

      EXPLAIN的输出总是有相同的列(只有EXPLAIN EXTENDED在MySQL5.1中增加了一个filtered列,EXPLAIN PARTITIONS增加了一个Partitions列)。可变的是行数及内容。然而,为了保持我们的例子简洁明了,我们在本附录中不总是显示所有的列。

      在接下来的小节中,我们将展示在EXPLAIN结果中每一列的意义。记住,输出中的行以MySQL实际执行的査询部分的顺序出现,而这个顺序不总是与其在原始SQL中的相一致。

     

    2.1 id列

      这一列总是包含一个编号,标识SELECT所属的行。如果在语句当中没有子查询或联合,那么只会有唯一的SELECT,于是每一行在这个列中都将显示一个1。否则,内层的SELECT语句一般会顺序编号,对应于其在原始语句中的位置。

      MySQL将SELECT査询分为简单和复杂类型,复杂类型可分成三大类:简单子査询、所谓的派生表(在FROM子句中的子査询),以及UNION査询。下面是一个简单的子査询。

    mysql> EXPLAIN SELECT (SELECT 1 FROM sakila.actor LIMIT 1) FROM sakila.film;
    +----+-------------+-------+...
    | id | select_type | table |...
    +----+-------------+-------+...
    |  1 | PRIMARY     | film  |...
    |  2 | SUBQUERY    | actor |...
    +----+-------------+-------+...

      FROM子句中的子査询和联合给id列增加了更多复杂性。下面是一个FROM子句中的基本子査询。

    mysql> EXPLAIN SELECT film_id FROM (SELECT film_id FROM sakila.film) AS der;
    +----+-------------+------------+...
    | id | select_type | table      |...
    +----+-------------+------------+...
    |  1 | PRIMARY     | <derived2> |...
    |  2 | DERIVED     | film       |...
    +----+-------------+------------+...

      如你所知,这个査询执行时有一个匿名临时表。MySQL内部通过别名(der)在外层査询中引用这个临时表,在更复杂的査询中可以看到ref列。

      最后,下面是一个UNION査询。

    mysql> EXPLAIN SELECT 1 UNION ALL SELECT 1;
    +------+--------------+------------+...
    | id   | select_type  | table      |...
    +------+--------------+------------+...
    |  1   | PRIMARY      | NULL       |...
    |  2   | UNION        | NULL       |...
    | NULL | UNION RESULT | <union1,2> |...
    +------+--------------+------------+...

      注意UNION结果输出中的额外行。UNION结果总是放在一个匿名临时表中,之后MySQL将结果读取到临时表外。临时表并不在原SQL中出现,因此它的id列是NULL。与之前的例子相比(演示子査询的那个FROM子句中),从这个査询产生的临时表在结果中出现在最后一行,而不是第一行。

      到目前为止这些都非常直截了当,但这三类语句的混合则会使输出变得非常复杂,我们稍后就会看到。

     

    2.2 select_type列

      这一列显示了对应行是简单还是复杂SELECT(如果是后者,那么是三种复杂类型中的哪一种)。SIMPLE值意味着査询不包括子査询和UNION。如果査询有任何复杂的子部分,则最外层部分标记为PRIMARY,其他部分标记如下。

      SUBQUERY

    包含在SELECT列表中的子査询中的SELECT(换句话说,不在FROM子句中)标记为SUBQUERY。

      DERIVED

    DERIVED值用来表示包含在FROM子句的子査询中的SELECT,MySQL会递归执行并将结果放到一个临时表中。服务器内部称其“派生表”,因为该临时表是从子査询中派生来的。

      UNION

    在UNION中的第二个和随后的SELECT被标记为UNION。第一个SELECT被标记就好像它以部分外査询来执行。这就是之前的例子中在UNION中的第一个SELECT显示为PRIMARY的原因。如果UNION被FROM子句中的子査询包含,那么它的第一个SELECT会被标记为DERIVED。

      UNION RESULT

    用来从UNION的匿名临时表检索结果的SELECT被标记为UNION RESULT。

      除了这些值,SUBQUERY和UNION还可以被标记为DEPENDENT和UNCACHEABLE。DEPENDENT意味着SELECT依赖于外层査询中发现的数据;UNCACHEABLE意味着SELECT中的某些特性阻止结果被缓存于一个Item_cache中。(Item_cache未被文档记载;它与査询缓存不是一回事,尽管它可以被一些相同类型的构件否定,例如RAND()函数。)

    2.3 table列

      这一列显示了对应行正在访问哪个表。在通常情况下,它相当明了:它就是那个表,或是该表的别名(如果SQL中定义了别名)。 

      可以在这一列中从上往下观察MySQL的关联优化器为査询选择的关联顺序。例如,可以看到在下面的査询中MySQL选择的关联顺序不同于语句中所指定的顺序。

    mysql> EXPLAIN SELECT film.film_id
        -> FROM sakila.film
        ->    INNER JOIN sakila.film_actor USING(film_id)
        ->    INNER JOIN sakila.actor USING(actor_id);
    +----+-------------+------------+...
    | id | select_type | table      |...
    +----+-------------+------------+...
    |  1 | SIMPLE      | actor      |...
    |  1 | SIMPLE      | film_actor |...
    |  1 | SIMPLE      | film       |...
    +----+-------------+------------+...

      想起<查询性能优化>章节展示的左侧深度优先(left-deep)树了吗?MySQL的査询执行计划总是左侧深度优先树。如果把这个计划放倒,就能按顺序读出叶子节点,它们直接对应于EXPLAIN中的行。

     

      派生表和联合

      当FROM子句中有子査询或有UNION时,table列会变得复杂得多。在这些场景下,确实没有一个“表”可以参考到,因为MySQL创建的匿名临时表仅在査询执行过程中存在。

      当在FROM子句中有子査询时,table列是<deriverdN>的形式,其中N是子査询的id。这总是“向前引用”——换言之,N指向EXPLAIN输出中后面的一行。

      当有UNION时,UNION RESULT的table列包含一个参与UNION的id列表。这总是“向后引用”,因为UNION RESULT出现在UNION中所有参与行之后。如果在列表中有超过20个id,table列可能被截断以防止太长,此时不可能看到所有的值。幸运的是,仍然可以推测包括哪些行,因为你可以看到第一行的id。在这一行和UNION RESULT之间出现的一切都会以某种方式被包含。

     

      一个复杂SELECT类型的例子

      下面是一个无意义的査询,我们这里把它用作某种复杂SELECT类型的紧凑示例。

     1  EXPLAIN
     2  SELECT actor_id,
     3     (SELECT 1 FROM sakila.film_actor WHERE film_actor.actor_id =
     4        der_1.actor_id LIMIT 1)
     5  FROM (
     6     SELECT actor_id
     7     FROM sakila.actor LIMIT 5
     8  ) AS der_1
     9  UNION ALL
    10  SELECT film_id,
    11     (SELECT @var1 FROM sakila.rental LIMIT 1)
    12  FROM (
    13     SELECT film_id,
    14        (SELECT 1 FROM sakila.store LIMIT 1)
    15     FROM sakila.film LIMIT 5
    16  ) AS der_2;

      LIMIT子句只是为了方便起见,以防你打算不以EXPLAIN方式执行来看结果。下面是EXPLAIN的结果。

    +------+----------------------+------------+...
    | id   | select_type          | table      |...
    +------+----------------------+------------+...
    |  1   | PRIMARY              | <derived3> |...
    |  3   | DERIVED              | actor      |...
    |  2   | DEPENDENT SUBQUERY   | film_actor |...
    |  4   | UNION                | <derived6> |...
    |  6   | DERIVED              | film       |...
    |  7   | SUBQUERY             | store      |...
    |  5   | UNCACHEABLE SUBQUERY | rental     |...
    | NULL | UNION RESULT         | <union1,4> |...
    +------+----------------------+------------+...

      我们特意让每个査询部分访问不同的表,以便可以弄清问题所在,但仍然难以解决!从最上面开始看。

    • 第1行向前引用了der_1,这个査询被标记为<derived3>。在原SQL中是第2行。想了解输出中哪些行引用了<derived3>中的SELECT语句,往下看... ...
    • ... ...第2行,它的id是3。因为它是査询中第3个SELECT的一部分,归为DERIVED类型是因为它嵌套在FROM子句中的子査询内部。在原SQL中为第6〜7行。
    • 第3行的id为2。在原SQL中为第3行。注意,它在具有更高id的行后面,暗示后面再执行,这是合理的。它被归为DEPENDENT SUBQUERY,意味着其结果依赖于外层査询(亦即某个相关子査询)。本例中的外査询是从第2行开始,从der_1中检索数据的SELECT。
    • 第4行被归为UNION,意味着它是UNION中的第2个或之后的SELECT。它的表为<derived6>,意味着是从子句FROM的子査询中检索数据并附加到UNION的临时表。像之前一样,要找到显示这个子査询的査询计划的EXPLAIN行,必须往下看。
    • 第5行是在原SQL中第13、14和15行定义的der_2子査询,EXPLAIN称其为<derived6>。
    • 第6行是<derived6>的SELECT(SELECT列表中的一个普通子査询,它的id为7,这非常重要... ...
    • ... ...因为它比5大,而5是第7行的id。为什么重要?因为它显示了<derived6>子查询的边界。当EXPLAIN输出SELECT类型为DERIVED的一行时,表示一个“嵌套范围”开始。如果后续行的id更小(本例中,5小于6),意味着嵌套范围已经被关闭。这就让我们知道第7行是从<derived6>中检索数据的SELECT列表中的部分——例如,第4行的SELECT列表的一部分(原SQL中第11行)。这个例子相当容易理解,不需要知道嵌套范围的意义和规则,当然有时候并不是这么容易。关于输出中的这一行另外一个要注意的是,因为有用户变量,它被列为UNCACHEABLE SUBQUERY。
    • 最后一行是UNION RESULT。它代表从UNION的临时表中读取行的阶段。你可以从这行开始反过来向后,如果你愿意的话。它会返回id是1和4的行结果,它们分别引用了<derived3>和<derived6>。

      如你所见,这些复杂的SELECT类型的组合会使EXPLAIN的输出相当难懂。理解规则会使其简单些,但仍然需要多实践。

      阅读EXPLAIN的输出经常需要在列表中跳来跳去。例如,再査看第一行输出。仅仅盯着看,是无法知道它是UNION的一部分的。只有看到最后一行你才会明白过来。

    2.4 type列

      MySQL用户手册上说这一列显示了“关联类型”,但我们认为更准确的说法是访问类型——换言之就是MySQL决定如何査找表中的行。下面是最重要的访问方法,依次从最差到最优。

      ALL 

    这就是人们所称的全表扫描,通常意味着MySQL必须扫描整张表,从头到尾,去找到需要的行。(这里也有个例外,例如在査询里使用了LIMIT,或者在Extra列中显示”Using distinct/not exists”。) 

      index

    这个跟全表扫描一样,只是MySQL扫描表时按索引次序进行而不是行。它的主要优点是避免了排序;最大的缺点是要承担按索引次序读取整个表的开销。这通常意味着若是按随机次序访问行,开销将会非常大。

    如果在Extra列中看到“Using index”,说明MySQL正在使用覆盖索引,它只扫描索引的数据,而不是按索引次序的每一行。它比按索引次序全表扫描的开销要少很多。 

      range

    范围扫描就是一个有限制的索引扫描,它开始于索引里的某一点,返回匹配这个值域的行。这比全索引扫描好一些,因为它用不着遍历全部索引。显而易见的范围扫描是带有BETWEEN或在WHERE子句里带有 > 的査询。

    当MySQL使用索引去査找一系列值时,例如IN()和OR列表,也会显示为范围扫描。然而,这两者其实是相当不同的访问类型,在性能上有重要的差异。<创建高性能索引>文章“什么是范围条件”。

    此类扫描的开销跟索引类型相当。 

      ref

    这是一种索引访问(有时也叫做索引査找),它返回所有匹配某个单个值的行。然而,它可能会找到多个符合条件的行,因此,它是査找和扫描的混合体。此类索引访问只有当使用非唯一性索引或者唯一性索引的非唯一性前缀时才会发生。把它叫做ref是因为索引要跟某个参考值相比较。这个参考值或者是一个常数,或者是来自多表査询前一个表里的结果值。

    ref_or_null是ref之上的一个变体,它意味着MySQL必须在初次查找的结果里进行第二次査找以找出NULL条目。

      eq-ref

    使用这种索引査找,MySQL知道最多只返回一条符合条件的记录。这种访问方法可以在MySQL使用主键或者唯一性索引査找时看到,它会将它们与某个参考值做比较。MySQL对于这类访问类型的优化做得非常好,因为它知道无须估计匹配行的范围或在找到匹配行后再继续査找。 

      const,system

    当MySQL能对査询的某部分进行优化并将其转换成一个常量时,它就会使用这些访问类型。举例来说,如果你通过将某一行的主键放入WHERE子句里的方式来选取此行的主键,MySQL就能把这个査询转换为一个常量。然后就可以高效地将表从联接执行中移除。

      NULL

    这种访问方式意味着MySQL能在优化阶段分解査询语句,在执行阶段甚至用不着再访问表或者索引。例如,从一个索引列里选取最小值可以通过单独査找索引来完成,不需要在执行时访问表。

     

    2.5 possible_keys

      这一列显示了査询可以使用哪些索引,这是基于査询访问的列和使用的比较操作符来判断的。这个列表是在优化过程的早期创建的,因此有些罗列出来的索引可能对于后续优化过程是没用的。

     

    2.6 key列

      这一列显示了MySQL决定采用哪个索引来优化对该表的访问。如果该索引没有出现在possible_keys列中,那么MySQL选用它是出于另外的原因-例如,它可能选择了一个覆盖索引,哪怕没有WHERE子句。

      换句话说,possible_keys掲示了哪一个索引能有助于高效地行査找,而key显示的是优化采用哪一个索引可以最小化査询成本。下面就是一个例子。

    mysql> EXPLAIN SELECT actor_id, film_id FROM sakila.film_actor\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: film_actor
             type: index
    possible_keys: NULL
              key: idx_fk_film_id
          key_len: 2
              ref: NULL
             rows: 5143
            Extra: Using index

    2.7 key_len列

      该列显示了MySQL在索引里使用的字节数。如果MySQL正在使用的只是索引里的某些列,那么就可以用这个值来算出具体是哪些列。要记住,MySQL5.5及之前版本只能使用索引的最左前缀。举例来说,sakila.film_actor的主键是两个SMALLINT列,并且每个SMALLINT列是两字节,那么索引中的每项是4字节。以下就是一个查询的示例:

    mysql> EXPLAIN SELECT actor_id, film_id FROM sakila.film_actor WHERE actor_id=4;
    ...+------+---------------+---------+---------+...
    ...| type | possible_keys | key     | key_len |...
    ...+------+---------------+---------+---------+...
    ...| ref  | PRIMARY       | PRIMARY | 2       |...
    ...+------+---------------+---------+---------+...

      基于结果中的key_len列,可以推断出査询使用唯一的首列一一actor_id列,来执行索引查找。当我们计算列的使用情况时,务必把字符列中的字符集也考虑进去。

    mysql> CREATE TABLE t (
        ->    a char(3) NOT NULL,
        ->    b int(11) NOT NULL,
        ->    c char(1) NOT NULL,
        ->    PRIMARY KEY  (a,b,c)
        -> ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ;
    mysql> INSERT INTO t(a, b, c)
        ->    SELECT DISTINCT LEFT(TABLE_SCHEMA, 3), ORD(TABLE_NAME),
        ->       LEFT(COLUMN_NAME, 1)
        ->    FROM INFORMATION_SCHEMA.COLUMNS:
    mysql> EXPLAIN SELECT a FROM t WHERE a='sak' AND b = 112;
    ...+------+---------------+---------+---------+...
    ...| type | possible_keys | key     | key_len |...
    ...+------+---------------+---------+---------+...
    ...| ref  | PRIMARY       | PRIMARY | 13      |...
    ...+------+---------------+---------+---------+...

      这个査询中平均长度为13字节,即为a列和b列的总长度。a列是3个字符,utf8下每一个最多为3字节,而b列是一个4字节整型。_

      MySQL并不总显示一个索引真正使用了多少。例如,如果对一个前缀模式匹配执行LIKE査询,它会显示列的完全宽度正在被使用。

      key_len列显示了在索引字段中可能的最大长度,而不是表中数据使用的实际字节数。在前面例子中MySQL总是显示13字节,即使a列恰巧只包含一个字符长度。换言之,key_len通过査找表的定义而被计算出,而不是表中的数据。

     

    2.8 ref列

      这一列显示了之前的表在key列记录的索引中査找值所用的列或常量。下面是一个展示关联条件和别名组合的例子。注意,ref列反映了在査询文本中film表是如何以f为別名的。

    mysql> EXPLAIN
        -> SELECT STRAIGHT_JOIN f.film_id
        -> FROM sakila.film AS f
        ->    INNER JOIN sakila.film_actor AS fa
        ->       ON f.film_id=fa.film_id AND fa.actor_id = 1
        ->    INNER JOIN sakila.actor AS a USING(actor_id);
    ...+-------+...+--------------------+---------+------------------------+...
    ...| table |...| key                | key_len | ref                    |...
    ...+-------+...+--------------------+---------+------------------------+...
    ...| a     |...| PRIMARY            | 2       | const                  |...
    ...| f     |...| idx_fk_language_id | 1       | NULL                   |...
    ...| fa    |...| PRIMARY            | 4       | const,sakila.f.film_id |...
    ...+-------+...+--------------------+---------+------------------------+...

     

    2.9 rows列

      这一列是MySQL估计为了找到所需的行而要读取的行数。这个数字是内嵌循环关联计划里的循环数目。也就是说它不是MySQL认为它最终要从表里读取出来的行数,而是MySQL为了找到符合査询的每一点上标准的那些行而必须读取的行的平均数。(这个标准包括SQL里给定的条件,以及来自联接次序上前一个表的当前列。)

      根据表的统计信息和索引的选用情况,这个估算可能很不精确。在MySQL5.0及更早的版本里,它也反映不出LIMIT子句。举例来说,下面这个査询不会真的检査1022行。

    mysql> EXPLAIN SELECT * FROM sakila.film LIMIT 1\G
    ...
             rows: 1022

      通过把所有rows列的值相乘,可以粗略地估算出整个査询会检査的行数。例如,以下这个査询大约会检査2600行。

    mysql> EXPLAIN
        -> SELECT f.film_id
        -> FROM sakila.film AS f
        ->    INNER JOIN sakila.film_actor AS fa USING(film_id)
        ->    INNER JOIN sakila.actor AS a USING(actor_id);
    ...+------+...
    ...| rows |...
    ...+------+...
    ...|  200 |...
    ...|   13 |...
    ...|    1 |...
    ...+------+...

      要记住这个数字是MySQL认为它要检査的行数,而不是结果集里的行数。同时也要认识到有很多优化手段,例如关联缓冲区和缓存,无法影响到行数的显示。MySQL可能不必真的读所有它估计到的行,它也不知道任何关于操作系统或硬件缓存的信息。

     

    2.10 filtered列

      这一列是在MySQL5.1里新加进去的,在使用EXPLAIN EXTENDED时出现。它显示的是针对表里符合某个条件(WHERE子句或联接条件)的记录数的百分比所做的一个悲观估算。如果你把rows列和这个百分比相乘,就能看到MySQL估算它将和査询计划里前一个表关联的行数。在mysql5.5时优化器只有在使用ALL、index、range和index_merge访问方法时才会用这一估算。

      为了说明这一列的输出形式,我们创建了下面这样一张表。

    CREATE TABLE t1 (
       id INT NOT NULL AUTO_INCREMENT,
       filler char(200),
       PRIMARY KEY(id)
    );

      然后,我们往表里插入1000行记录,并在filler列里随机填充一些文字。它的用途是防止MySQL在我们将要运行的査询里使用覆盖索引。

    mysql> EXPLAIN EXTENDED SELECT * FROM t1 WHERE id < 500\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: t1
             type: ALL
    possible_keys: PRIMARY
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 1000
         filtered: 49.40
            Extra: Using where

      MySQL可以使用范围访问从表里获取到所有ID不超过500的行,但是,它没这么做,这是因为那样只能去除大约一半的记录,它认为全表扫描也不是太昂贵。因此,它使用了全表扫描和WHERE子句来过滤输出行。它知道使用WHERE子句可以从结果里过滤掉多少条记录,因为范围访问的成本是可以估算出来的。这也就是49.40%出现在filtered列上的原因。

     

    2.11 Extra列

      这一列包含的是不适合在其他列显示的额外信息。MySQL用户手册里记录了大多数可以在这里出现的值。

      常见的最重要的值如下。

      “Using index

    此值表示MySQL将使用覆盖索引,以避免访问表。不要把覆盖索引和index访问类型弄混了。

      “Using where

    这意味着MySQL服务器将在存储引擎检索行后再进行过滤。许多WHERE条件里涉及索引中的列,当(并且如果)它读取索引时,就能被存储引擎检验,因此不是所有带WHERE子句的査询都会显示“Using where”。有时“Using where”的出现就是一个暗示:査询可受益于不同的索引。

      “Using temporary

    这意味着MySQL在对査询结果排序时会使用一个临时表。

      “Using filesort

    这意味着MySQL会对结果使用一个外部索引排序,而不是按索引次序从表里读取行。MySQL有两种文件排序算法。两种方式都可以在内存或磁盘上完成。EXPLAIN不会告诉你MySQL将使用哪一种文件排序,也不会告诉你排序会在内存里还是磁盘上完成。

      “Range checked for each record (index map:N)

    这个值意味着没有好用的索引,新的索引将在联接的每一行上重新估算。N是显示在possible_keys列中素引的位图,并且是冗余的。

     

    3.树形格式的输出

      MySQL用户往往更希望把EXPLAIN的输出格式化成一棵树,更加精确地展示执行计划。实际上,EXPLAIN査看査询计划的方式确实有点笨拙,树状结构也不适合表格化的输出。

      当Extra列里有大量的值时,缺点更明显,使用UNION也是这样。UNION跟MySQL能做的其他类型的联接不太一样,它不太适合EXPLAIN。

      如果对EXPLAIN的规则和特性有充分的了解,使用树形结构的执行计划也是可行的。但是这有点枯燥,最好还是留给自动化的工具处理。Percona Toolkit包含了pt-visual-explain它就是这样一个工具。

     

    4.MySQL5.6中的改进

      MySQL5.6中将包括一个对EXPLAIN的重要改进:能对类似UPDATE、INSERT等的査询进行解释。尽管可以将DML语句转化为准等价的“SELECT”查询并EXPLAIN,但结果并不会完全反映语句是如何执行的,因而这仍然非常有帮助。在开发使用类似Perccma Toolkit中的pt-upgrade时曾尝试使用过那个技术,我们不止一次发现,在将査询转化为SELECT时,优化器并不能按我们预期的代码路径执行。因而,EXPLAIN—个査询而不需要转化为SELECT,对我们理解执行过程中到底发生什么,是非常有帮助的。

      MySQL5.6还将包括对査询优化和执行引擎的一系列改进,允许匿名的临时表尽可能晚地被具体化,而不总是在优化和执行使用到此临时表的部分査询时创建并填充它们。这将允许MySQL可以直接解释带子査询的査询语句,而不需要先实际地执行子査询。

      最后,MySQL5.6将通过在服务器中增加优化跟踪功能的方式改进优化器的相关部分。这将允许用户査看优化器做出的抉择,以及输入(例如,索引的基数)和抉择的原因。这非常有帮助,不仅仅对理解服务器选择的执行计划如此,对为什么选择这个计划也如此。

  • 相关阅读:
    java的构造方法 java程序员
    No result defined for action cxd.action.QueryAction and result success java程序员
    大学毕业后拉开差距的真正原因 java程序员
    hibernate的回滚 java程序员
    验证码 getOutputStream() has already been called for this response异常的原因和解决方法 java程序员
    浅谈ssh(struts,spring,hibernate三大框架)整合的意义及其精髓 java程序员
    你平静的生活或许会在某个不可预见的时刻被彻底打碎 java程序员
    Spring配置文件中使用ref local与ref bean的区别. 在ApplicationResources.properties文件中,使用<ref bean>与<ref local>方法如下 java程序员
    poj1416Shredding Company
    poj1905Expanding Rods
  • 原文地址:https://www.cnblogs.com/lizexiong/p/15573760.html
Copyright © 2011-2022 走看看