MySLQ执行计划介绍

MySQL Query Execution Plan

Posted by qin4zhang on January 11, 2020

Hey

这是我的第一篇博客入门文章。

不积跬步无以至千里

MySQL的执行计划

本文记录MySQL的执行计划介绍,更多用法,可以参考MySQL文档

根据表,列,索引的详细信息以及WHERE子句中的条件,MySQL优化器考虑了许多技术来有效执行SQL查询中涉及的查找。 无需读取所有行即可执行对巨大表的查询; 可以执行涉及多个表的联接,而无需比较行的每个组合。 优化器选择执行最有效查询的一组操作称为“查询执行计划”,也称为EXPLAIN计划。 您的目标是要认识到EXPLAIN计划的各个方面,这些方面指示查询已被优化,并且如果发现一些低效的操作,则将学习SQL语法和索引技术以改进计划。

使用EXPLAIN优化查询

EXPLAIN语句提供有关MySQL如何执行语句的信息:

  • EXPLAIN与SELECT,DELETE,INSERT,REPLACE和UPDATE语句一起使用。
  • 当EXPLAIN与可解释的语句一起使用时,MySQL将显示来自优化程序的有关语句执行计划的信息。 也就是说,MySQL解释了它将如何处理该语句,包括有关如何连接表以及以何种顺序连接表的信息。
  • 当EXPLAIN与FOR CONNECTION connection_id而不是可解释的语句一起使用时,它将显示在命名连接中执行的语句的执行计划。
  • 对于SELECT语句,EXPLAIN会生成其他执行计划信息,可以使用SHOW WARNINGS显示这些信息。
  • EXPLAIN对于检查涉及分区表的查询很有用。
  • FORMAT选项可用于选择输出格式。 TRADITIONAL以表格格式显示输出。如果不存在FORMAT选项,则为默认设置。 JSON格式以JSON格式显示信息。

借助EXPLAIN,您可以看到应该在哪里向表添加索引,以便通过使用索引查找行来使语句更快地执行。 您还可以使用EXPLAIN来检查优化器是否以最佳顺序联接表。 要提示优化器使用与SELECT语句中表的命名顺序相对应的连接顺序,请以SELECT STRAIGHT_JOIN而不是SELECT开头。 但是,STRAIGHT_JOIN可能会禁止使用索引,因为它禁用了半联接转换。

优化程序跟踪有时可能提供与EXPLAIN信息互补的信息。但是,优化程序的跟踪格式和内容在版本之间可能会发生变化。

如果您在认为应该使用索引时遇到问题,请运行ANALYZE TABLE更新表统计信息,例如键的基数,这可能会影响优化器的选择。

注意 EXPLAIN也可用于获取有关表中列的信息。 EXPLAIN tbl_name与DESCRIBE tbl_name和SHOW COLUMNS FROM tbl_name相同。

执行计划格式化输出

EXPLAIN语句提供有关MySQL如何执行语句的信息。 EXPLAIN与SELECT,DELETE,INSERT,REPLACE和UPDATE语句一起使用。

EXPLAIN为SELECT语句中使用的每个表返回一行信息。 它按照MySQL在处理语句时读取它们的顺序列出了输出中的表。 MySQL使用嵌套循环连接方法解析所有连接。 这意味着MySQL从第一个表中读取一行,然后在第二个表,第三个表中找到匹配的行,依此类推。 处理完所有表后,MySQL通过表列表输出选定的列和回溯,直到找到一个表,其中存在更多匹配的行。 从该表中读取下一行,然后继续下一个表。

EXPLAIN输出包括分区信息。同样,对于SELECT语句,EXPLAIN生成扩展信息,在EXPLAIN之后可以与SHOW WARNINGS显示.

执行计划输出列

本节描述EXPLAIN产生的输出列。后面的部分提供有关type和Extra列的其他信息。

EXPLAIN的每个输出行都提供有关一个表的信息。列名显示在表的第一列中;第二列提供使用FORMAT = JSON时在输出中显示的等效属性名称。具体的列信息见下表:

列名 JSON 名 含义
id select_id 每个select子句的标识id
select_type None select语句的类型
table table_name 当前表名
partitions partitions 匹配的分区
type access_type 当前表内访问方式 join type
possible_keys possible_keys 可能选择的索引
key key 经过优化器评估最终使用的索引
key_len key_length 使用到的索引长度
ref ref 引用到的上一个表的列
rows rows 要得到最终记录所要扫描经过的记录数
filtered filtered 按表条件过滤行的百分比
Extra None 额外的信息说明
  • id

    SELECT标识符。 这是查询中SELECT的顺序号。 如果该行引用其他行的并集结果,则该值可以为NULL。 在这种情况下,表列显示类似<unionM,N>的值,以指示该行引用ID值为M和N的行的并集。

  • select_type

    SELECT的类型,可以是下表中显示的任何类型。 JSON格式的EXPLAIN会将SELECT类型公开为query_block的属性,除非它为SIMPLE或PRIMARY。 表格中还显示了JSON名称(如果适用)。

    select_type的值 JSON名称 含义
    SIMPLE None 简单的SELECT语句(不包括UNION操作或子查询操作)
    PRIMARY None PRIMARY:查询中最外层的SELECT(如两表做UNION或者存在子查询的外层的表操作为PRIMARY,内层的操作为UNION)
    UNION None UNION:UNION操作中,查询中处于内层的SELECT(内层的SELECT语句与外层的SELECT语句没有依赖关系)
    DEPENDENT UNION dependent(true) DEPENDENT UNION:UNION操作中,查询中处于内层的SELECT(内层的SELECT语句与外层的SELECT语句有依赖关系)
    UNION RESULT union_result UNION RESULT:UNION操作的结果,id值通常为NULL
    SUBQUERY None SUBQUERY:子查询中首个SELECT(如果有多个子查询存在)
    DEPENDENT SUBQUERY dependent(true) DEPENDENT SUBQUERY:子查询中首个SELECT,但依赖于外层的表(如果有多个子查询存在)
    DERIVED None DERIVED:被驱动的SELECT子查询(子查询位于FROM子句)
    MATERIALIZED materialized_form_subquery MATERIALIZED:被物化的子查询
    UNCACHEABLE SUBQUERY cacheable(false) UNCACHEABLE SUBQUERY:对于外层的主表,子查询不可被物化,每次都需要计算(耗时操作)
    UNCACHEABLE UNION cacheable(false) UNCACHEABLE UNION:UNION操作中,内层的不可被物化的子查询(类似于UNCACHEABLE SUBQUERY)

    DEPENDENT通常表示关联子查询的使用。

    DEPENDENT SUBQUERY评估与UNCACHEABLE SUBQUERY评估不同。 对于DEPENDENT SUBQUERY,子查询仅针对其外部上下文中变量的每组不同值重新评估一次。 对于UNCACHEABLE SUBQUERY,将为外部上下文的每一行重新评估子查询。

    当使用EXPLAIN指定FORMAT = JSON时,输出不具有直接等同于select_type的单个属性; query_block属性对应于给定的SELECT。 可以使用与刚刚显示的大多数SELECT子查询类型等效的属性(示例是MATERIALIZED的materialized_from_subquery),并在适当时显示。 没有与SIMPLE或PRIMARY等效的JSON。

    非SELECT语句的select_type值显示受影响表的语句类型。例如,对于DELETE语句,select_type是DELETE。

  • table

    输出行所引用的表的名称。这也可以是以下值之一:

     - \<unionM,N\>:该行引用ID值为M和N的行的并集。
     - \<derivedN\>:该行引用ID值为N的行的派生表结果。派生表可能来自例如FROM子句中的子查询。
     - \<subqueryN\>:该行引用ID为N的行的实例化子查询的结果。
    
  • partitions

    查询将从中匹配记录的分区。对于非分区表,该值为NULL。

  • type

    join type,具体的不同参见下述描述。

  • possible_keys

    Possible_keys列指示MySQL可以从中选择的索引,以查找此表中的行。 请注意,此列完全独立于EXPLAIN输出中显示的表顺序。 这意味着在实践中可能无法将某些键与生成的表顺序一起使用。

    如果此列为NULL(或在JSON格式的输出中未定义),则没有相关的索引。 在这种情况下,您可以通过检查WHERE子句来检查它是否引用了某些适合索引的列,从而可以提高查询性能。 如果是这样,请创建一个适当的索引,然后再次使用EXPLAIN检查查询。

    若要查看表具有的索引,请使用SHOW INDEX FROM tbl_name。

  • key

    key列指示MySQL实际决定使用的索引。如果MySQL决定使用possible_keys索引之一来查找行,则将该索引列为键值。

    键可能会命名一个可能索引中不存在的索引。 如果可能的索引关键字都不适合查找行,但是查询选择的所有列都是其他索引的列,则可能发生这种情况。 也就是说,命名索引覆盖了选定的列,因此尽管不使用索引来确定要检索的行,但索引扫描比数据行扫描更有效。

    对于InnoDB,即使查询也选择了主键,辅助索引也可能覆盖选定的列,因为InnoDB将主键值与每个辅助索引一起存储。 如果key为NULL,则MySQL没有找到索引可用于更有效地执行查询。

    要强制MySQL使用或忽略mays_keys列中列出的索引,请在查询中使用FORCE INDEX,USE INDEX或IGNORE INDEX。

    对于MyISAM表,运行ANALYZE TABLE可帮助优化器选择更好的索引。对于MyISAM表,myisamchk –analyze进行相同的操作。

  • key_len

    key_len列指示MySQL决定使用的密钥的长度。 key_len的值使您能够确定MySQL实际使用的多部分键的多少部分。如果key列为NULL,则key_len列也为NULL。

  • ref

    ref列显示将哪些列或常量与键列中命名的索引进行比较以从表中选择行。

    如果该值为func,则使用的值是某些函数的结果。要查看哪个功能,请在EXPLAIN之后使用SHOW WARNINGS来查看扩展的EXPLAIN输出。该函数实际上可能是算术运算符之类的运算符。

  • rows

    rows列表示MySQL认为执行查询必须检查的行数。

    对于InnoDB表,此数字是估计值,可能并不总是准确的。

  • filtered

    已过滤的列指示按表条件过滤的表行的估计百分比。 最大值为100,这表示未过滤行。 值从100减小表示过滤量增加。 rows显示检查的行的估计数,×过滤的行显示与下表连接的行数。 例如,如果行数为1000,过滤条件为50.00(50%),则与下表连接的行数为1000×50%= 500。

  • Extra

    此列包含有关MySQL如何解析查询的其他信息。

    没有与Extra列相对应的JSON属性;但是,此列中可能出现的值显示为JSON属性或message属性的文本。

Explain Join Types

EXPLAIN输出的type列描述如何联接表。在JSON格式的输出中,将这些作为access_type属性的值。以下列表描述了连接类型,从最佳类型到最差类型:

  • system

    该表只有一行(=系统表)。这是const join类型的特例。

  • const

    该表最多具有一个匹配行,该行在查询开始时读取。 因为只有一行,所以优化器的其余部分可以将这一行中列的值视为常量。 const表非常快,因为它们只能读取一次。

    当您将PRIMARY KEY或UNIQUE索引的所有部分与常量值进行比较时,将使用const。在以下查询中,tbl_name可以用作const表:

      SELECT * FROM tbl_name WHERE primary_key=1;
        
      SELECT * FROM tbl_name
        WHERE primary_key_part1=1 AND primary_key_part2=2;
    
  • eq_ref

    对于先前表中的每行组合,从此表中读取一行。 除了system和const类型,这是可能的最佳联接类型。 当连接使用索引的所有部分并且索引是PRIMARY KEY或UNIQUE NOT NULL索引时,将使用它。

    eq_ref可用于使用=运算符进行比较的索引列。 比较值可以是常量,也可以是使用在此表之前读取的表中列的表达式。 在以下示例中,MySQL可以使用eq_ref连接来处理ref_table:

      SELECT * FROM ref_table,other_table
        WHERE ref_table.key_column=other_table.column;
        
      SELECT * FROM ref_table,other_table
        WHERE ref_table.key_column_part1=other_table.column
        AND ref_table.key_column_part2=1;
    
  • ref

    对于先前表中的行的每种组合,将从该表中读取具有匹配索引值的所有行。 如果联接仅使用键的最左前缀,或者如果该键不是PRIMARY KEY或UNIQUE索引(换句话说,如果联接无法根据键值选择单个行),则使用ref。 如果使用的键仅匹配几行,则这是一种很好的联接类型。

    ref可以用于使用=或<=>运算符进行比较的索引列。在以下示例中,MySQL可以使用ref联接来处理ref_table:

      SELECT * FROM ref_table WHERE key_column=expr;
        
      SELECT * FROM ref_table,other_table
        WHERE ref_table.key_column=other_table.column;
        
      SELECT * FROM ref_table,other_table
        WHERE ref_table.key_column_part1=other_table.column
        AND ref_table.key_column_part2=1;
    
  • fulltext

    使用FULLTEXT索引执行连接。

  • ref_or_null

    这种连接类型类似于ref,但是MySQL会额外搜索包含NULL值的行。 此联接类型优化最常用于解析子查询。 在以下示例中,MySQL可以使用ref_or_null连接来处理ref_table:

      SELECT * FROM ref_table
        WHERE key_column=expr OR key_column IS NULL;
    
  • index_merge

    此join type 指示使用索引合并优化。在这种情况下,输出行中的键列包含使用的索引的列表,而key_len包含使用的索引的最长键部分的列表。

  • unique_subquery

    此类型将eq_ref替换为以下形式的某些IN子查询:

      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_len包含使用的最长的键部分。此类型的ref列为NULL。

    使用=,<>,>,> =,<,<=,IS NULL,<=>,BETWEEN,LIKE或IN()运算符将键列与常量进行比较时,可以使用range:

      SELECT * FROM tbl_name
        WHERE key_column = 10;
        
      SELECT * FROM tbl_name
        WHERE key_column BETWEEN 10 and 20;
        
      SELECT * FROM tbl_name
        WHERE key_column IN (10,20,30);
        
      SELECT * FROM tbl_name
        WHERE key_part1 = 10 AND key_part2 IN (10,20,30);
    
  • index

    index join type 与ALL相同,除了扫描索引树外。这发生两种方式: - 如果索引是查询的覆盖索引,并且可用于满足表中所需的所有数据,则仅扫描索引树。 在这种情况下,“额外”列显示“使用索引”。 仅索引扫描通常比ALL快,因为索引的大小通常小于表数据。 - 使用对索引的读取执行全表扫描,以按索引顺序查找数据行。 Uses索引未出现在Extra列中。

    当查询仅使用属于单个索引一部分的列时,MySQL可以使用此联接类型。

  • ALL

    对来自先前表的行的每个组合进行全表扫描。 如果该表是未标记为const的第一个表,则通常不好,并且在所有其他情况下通常非常糟糕。 通常,您可以通过添加索引来避免ALL,这些索引允许基于早期表中的常量值或列值从表中检索行。

EXPLAIN Extra Information

EXPLAIN输出的Extra列包含有关MySQL如何解析查询的其他信息。 以下列表说明了可以在此列中显示的值。 每个项目还为JSON格式的输出指示哪个属性显示Extra值。 对于其中一些,有一个特定的属性。 其他显示为message属性的文本。

如果要尽快查询,请注意“使用文件排序”和“使用临时”的Extra列值,或者在JSON格式的EXPLAIN输出中,将using_filesort和using_temporary_table属性设置为true。

EXPLAIN Output Interpretation

通过获取EXPLAIN输出的rows列中值的乘积,可以很好地表明联接的好坏。 这应该大致告诉您MySQL必须检查多少行才能执行查询。 如果使用系统变量max_join_size限制查询,则此行乘积还用于确定要执行哪些多表SELECT语句以及中止哪个多表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 Date 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)
  • tt.ActualPC值分布不均。

最初,在执行任何优化之前,EXPLAIN语句会产生以下信息:

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 (index map: 0x23)

因为每个表的类型都是ALL,所以此输出表明MySQL正在为所有表生成笛卡尔乘积。 也就是说,每行组合。 这需要相当长的时间,因为必须检查每个表中的行数的乘积。 对于当前情况,此乘积为74×2135×74×3872 = 45,268,558,720行。 如果表更大,您只能想象需要多长时间。

这里的一个问题是,如果将索引声明为相同的类型和大小,MySQL可以更有效地在列上使用索引。 在这种情况下,如果将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 (index map: 0x1)
et_1  ALL    PRIMARY       NULL    NULL    NULL        74
      Range checked for each record (index map: 0x1)
et    eq_ref PRIMARY       PRIMARY 15      tt.ActualPC 1

这不是完美的,但是好了很多:行值的乘积少了74倍。此版本在几秒钟内执行。

可以进行第二种更改以消除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分析key的分布:

mysql> ANALYZE TABLE tt;

使用附加的索引信息,join是完美的,并且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子句中以不同顺序列出表来获得更好的性能。 (但是,STRAIGHT_JOIN可能会禁止使用索引,因为它禁用了半联接转换。

扩展的EXPLAIN输出格式

对于SELECT语句,EXPLAIN语句会产生额外的(“扩展的”)信息,这些信息不是EXPLAIN输出的一部分,但可以通过在EXPLAIN之后发出SHOW WARNINGS语句来查看。 SHOW WARNINGS输出中的Message值显示优化器如何限定SELECT语句中的表名和列名,应用重写和优化规则后SELECT的外观以及有关优化过程的其他注意事项。

EXPLAIN之后可通过SHOW WARNINGS语句显示的扩展信息仅适用于SELECT语句。 SHOW WARNINGS对于其他可解释的语句(DELETE,INSERT,REPLACE和UPDATE)显示空结果。

这是扩展的EXPLAIN输出的示例:

mysql> EXPLAIN
       SELECT t1.a, t1.a IN (SELECT t2.a FROM t2) FROM t1\G
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: t1
         type: index
possible_keys: NULL
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 4
     filtered: 100.00
        Extra: Using index
*************************** 2. row ***************************
           id: 2
  select_type: SUBQUERY
        table: t2
         type: index
possible_keys: a
          key: a
      key_len: 5
          ref: NULL
         rows: 3
     filtered: 100.00
        Extra: Using index
2 rows in set, 1 warning (0.00 sec)

mysql> SHOW WARNINGS\G
*************************** 1. row ***************************
  Level: Note
   Code: 1003
Message: /* select#1 */ select `test`.`t1`.`a` AS `a`,
         <in_optimizer>(`test`.`t1`.`a`,`test`.`t1`.`a` in
         ( <materialize> (/* select#2 */ select `test`.`t2`.`a`
         from `test`.`t2` where 1 having 1 ),
         <primary_index_lookup>(`test`.`t1`.`a` in
         <temporary table> on <auto_key>
         where ((`test`.`t1`.`a` = `materialized-subquery`.`a`))))) AS `t1.a
         IN (SELECT t2.a FROM t2)` from `test`.`t1`
1 row in set (0.00 sec)

由于SHOW WARNINGS显示的语句可能包含特殊标记以提供有关查询重写或优化程序操作的信息,因此该语句不一定是有效的SQL,也不打算执行。 输出还可能包含带有Message值的行,这些值提供有关优化程序采取的操作的其他非SQL解释性说明。

以下列表描述了可以在SHOW WARNINGS显示的扩展输出中出现的特殊标记:

  • 自动生成的临时表key。
  • (expr) 表达式(例如标量子查询)执行一次,结果值保存在内存中以备后用。对于包含多个值的结果,可能会创建一个临时表,而您可能会看到
  • (query fragment) 子查询谓词将转换为EXISTS谓词,然后将子查询转换为可与EXISTS谓词一起使用。
  • (query fragment) 这是一个内部优化器对象,对用户没有任何意义。
  • (query fragment) 使用索引查找来处理查询片段以查找合格的行。
  • (condition, expr1, expr2) 如果条件为真,则求值为expr1,否则为expr2。
  • (expr) 一种测试,以验证该表达式的计算结果是否不为NULL。
  • (query fragment) 使用子查询实现。
  • materialized-subquery.col_name

    实现了对内部临时表中col_name列的引用,以保存评估子查询的结果。

  • (query fragment) 使用主键查找来处理查询片段以查找合格的行。
  • (expr) 这是一个内部优化器对象,对用户没有任何意义。
  • /* select#N */ select_stmt

    SELECT与ID值为N的非扩展EXPLAIN输出中的行关联。

  • outer_tables semi join (inner_tables)

    半联接操作。 inner_tables显示未拉出的表。

  • <temporary table>

    这表示为缓存中间结果而创建的内部临时表。

当某些表为const或系统类型时,涉及这些表中列的表达式将由优化器尽早评估,并且不属于所显示语句的一部分。 但是,如果使用FORMAT = JSON,则某些const表访问将显示为使用const值的ref访问。

评估查询性能

在大多数情况下,您可以通过计算磁盘搜索次数来估计查询性能。 对于小型表,通常可以在一个磁盘搜索中找到一行(因为索引可能已缓存)。 对于更大的表,您可以估计,使用B树索引,您需要进行许多次查找才能找到行:log(row_count)/ log(index_block_length / 3 * 2 /(index_length + data_pointer_length))+ 1。

在MySQL中,索引块通常为1,024字节,数据指针通常为四个字节。对于键值长度为三个字节(MEDIUMINT的大小)的500,000行表,该公式表示log(500,000)/ log(1024/3 * 2 /(3 + 4))+ 1 = 4个搜索。

该索引将需要大约500,000 * 7 * 3/2 = 5.2MB的存储空间(假设典型的索引缓冲区填充率为2/3),因此您可能在内存中拥有很多索引,因此只需要一个或两个调用即可读取数据以查找行。

但是,对于写操作,您需要四个搜索请求来查找在何处放置新索引值,通常需要两个搜索来更新索引并写入行。

前面的讨论并不意味着您的应用程序性能会因日志N缓慢降低。只要所有内容都由OS或MySQL服务器缓存,随着表的增大,事情只会变得稍微慢一些。 在数据变得太大而无法缓存之后,事情开始变得缓慢得多,直到您的应用程序仅受磁盘搜索约束为止(磁盘log增长了log N)。 为避免这种情况,请随着数据的增长而增加key缓存的大小。 对于MyISAM表,密钥缓存大小由key_buffer_size系统变量控制。