ITPub博客

首页 > 数据库 > > 8.8 理解查询执行计划

8.8 理解查询执行计划

作者:飘絮絮絮丶 时间:2020-05-13 10:01:31 0 删除 编辑

参考官方文档:

dev.mysql.com/doc/refma




根据您的表,列,索引和WHERE子句中的条件的细节,MySQL优化器会考虑很多技术来高效地执行SQL查询中涉及的查找。 可以在不读取所有行的情况下执行对大表的查询。 可以执行涉及多个表的连接,而无需比较每个行的组合。 优化器选择执行最高效查询的一组操作称为“查询执行计划”,也称为EXPLAIN计划。 您的目标是识别EXPLAIN计划中表明查询已经优化的方面,并学习SQL语法和索引技术,以便在出现一些效率低下的操作时改进计划。



8.8.1 EXPLAN 中的优化器查询


EXPLAIN语句可用于获取有关MySQL如何执行语句的信息:


  • EXPLAIN使用SELECT,DELETE,INSERT,REPLACE和UPDATE语句。
  • 当EXPLAIN与可解释的语句一起使用时,MySQL会显示来自优化器的关于语句执行计划的信息。 也就是说,MySQL解释了它将如何处理该语句,包括有关如何连接表以及以何种顺序的信息。
  • 当EXPLAIN与FOR CONNECTION connection_id一起使用而不是可解释的语句时,它将显示在命名连接中执行的语句的执行计划
  • 在MySQL 5.7.3之前,可以使用EXPLAIN EXTENDED来获得额外的执行计划信息。默认情况下,扩展输出是启用的,EXTENDED关键字是不必要的。
  • 在MySQL 5.7.3之前,EXPLAIN PARTITIONS对于检查涉及分区表的查询很有用。 从MySQL 5.7.3开始,分区信息默认是启用的,PARTITIONS关键字是不必要的。
  • FORMAT选项可用于选择输出格式。 传统以表格形式呈现输出。 如果没有FORMAT选项,这是默认值。 JSON格式以JSON格式显示信息。



在EXPLAIN的帮助下,您可以看到应该在哪里添加索引,以便通过使用索引查找行来更快地执行语句。 您也可以使用EXPLAIN来检查优化器是否以最佳顺序加入表。 要提示优化器使用与SELECT语句中的表命名顺序相对应的连接顺序,请使用SELECT STRAIGHT_JOIN而不是SELECT开始语句。 (请参见第13.2.9节“SELECT语法”。)但是 STRAIGHT_JOIN可能会阻止使用索引,因为它会禁用半连接转换。


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


如果您在确定索引时没有使用索引时遇到问题,请运行ANALYZE TABLE以更新表格统计信息(如键的基数),这可能会影响优化程序的选择。


注意:

EXPLAIN也可以用来获取关于表中列的信息。 EXPLAIN tbl_name与DESCRIBE tbl_name和SHOW COLUMNS FROM tbl_name同义。




8.8.2 EXPLAIN 输出模式


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


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


--mysql 之中只有嵌套循环


在MySQL 5.7.3之前,当使用EXTENDED关键字时,EXPLAIN会产生额外的信息,可以通过在EXPLAIN语句后面发出SHOW WARNINGS语句来查看。 EXPLAIN EXTENDED还会显示已过滤的列。 从MySQL 5.7.3开始,扩展输出默认是启用的,EXTENDED关键字是不必要的。


注意:

在较早的MySQL版本中,使用EXPLAIN PARTITIONS和EXPLAIN EXTENDED生成分区和扩展信息。 这些语法仍然是为了向后兼容而被识别的,但是分区和扩展输出现在默认启用,所以PARTITIONS和EXTENDED关键字是多余的并且不推荐使用。 它们的使用会导致警告,并且它们将在未来的MySQL版本中从EXPLAIN语法中删除。



您不能在同一个EXPLAIN语句中同时使用过期的PARTITIONS和EXTENDED关键字。 另外,这些关键字都不能与FORMAT选项一起使用。(FORMAT=JSON会导致EXPLAIN自动显示扩展信息和分区信息;使用FORMAT=TRADITIONAL对EXPLAIN输出没有任何影响。)


MySQL Workbench具有Visual Explain功能,可以提供EXPLAIN输出的可视化表示。





EXPLAIN 输出字段

本节介绍EXPLAIN生成的输出列。 后面几节提供了关于类型和额外列的附加信息。


EXPLAIN的每个输出行提供关于一个表的信息。 每行都包含表8.1“汇总输出列”中汇总的值,并在表格后面详细介绍。 列名显示在表的第一列中; 当使用FORMAT = JSON时,第二列提供输出中显示的等效属性名称。





Column

JSON Name

Meaning

id

select_id

SELECT 标识符

select_type

None

SELECT 类型

table

table_name

输出行的表

partitions

partitions

匹配的分区

type

access_type

连接类型

possible_keys

possible_keys

最可能选择的索引

key

key

实际选择的索引key

key_len

key_length

选择索引key的长度

ref

ref

列与索引进行比较

rows

rows

要检查的行的估计

filtered

filtered

按表条件过滤的行的百分比

Extra

None

额外信息




注意:


在JSON格式的EXPLAIN输出中不显示JSON属性为NULL。



  • id (JSON name: select_id)

SELECT标识符。 这是查询中SELECT的连续编号。 如果行引用其他行的联合结果,则该值可以为NULL。 在这种情况下,表列显示一个值,例如<unionM,N>,表示该行是指具有M和N的id值的行的联合。


  • select_type(JSON名称:无)


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




select_typeValue

JSON Name

Meaning

SIMPLE

None


简单 SELECT(并没使用UNION 或子查询)





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


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


子查询的可缓存性不同于查询缓存中查询结果的缓存(在第8.10.3.1节“查询缓存如何操作”中有描述)。 子查询缓存发生在查询执行期间,而查询缓存仅在查询执行完成后用于存储结果。


当您使用EXPLAIN指定FORMAT=JSON时,输出没有直接等价于select_type的单个属性; query_block属性对应于给定的SELECT。 相当于刚刚显示的大多数SELECT子查询类型的属性都可用(例如,materialized的materialized_from_subquery),并在适当时显示。 对于SIMPLE或PRIMARY,没有JSON等价物。


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



  • table(JSON 名称: table_name)


输出行涉及的表的名称。 这也可以是以下值之一:




    • <unionM,N>:该行是指具有M和N的id值的行的union。
    • <derivedN>:该行引用id值为N的行的派生表结果。派生表可能导致(例如)FROM子句中的子查询。
    • <subqueryN>:该行引用id值为N的行的物化子查询的结果。
  • partitions (JSON 名称: partitions)


记录将与查询匹配的分区。 非分区表的值为NULL。



  • type (JSON 名称: access_type)


连接类型。



  • possible_keys (JSON 名称: possible_keys)


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


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


要查看表有哪些索引,请使用SHOW INDEX FROM tbl_name。




  • key (JSON name: key)



关键字列表示MySQL实际决定使用的关键字(索引)。 如果MySQL决定使用其中一个possible_keys索引来查找行,则该索引被列为关键值。


键可能会命名不在possible_keys值中的索引。 如果任何possible_keys索引都不适合查找行,则会发生这种情况,但查询所选的所有列都是某个其他索引的列。 也就是说,指定的索引覆盖了选定的列,因此虽然不用于确定要检索的行,但索引扫描比数据行扫描更有效。


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


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


对于MyISAM表,运行ANALYZE TABLE可以帮助优化器选择更好的索引。 对于MyISAM表格,myisamchk --analyze也是这样。


  • key_len (JSON name: key_length)



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


由于密钥的存储格式,关键字长度对于可以为NULL的列的长度大于对于NOT NULL列的关键字长度。



  • ref (JSON name: ref)


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

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



  • rows (JSON name: rows)


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

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



  • filtered (JSON name: filtered)


filtered的列表示将根据表条件过滤的表行的估计百分比。 也就是说,行显示了检查的估计行数,rows×filtered / 100显示了将与之前的表连接的行数。




  • Extra (JSON name: none)



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

没有与Extra列对应的单个JSON属性; 但是,此列中可能出现的值将作为JSON属性或作为消息属性的文本公开。



EXPLAIN JOin 类型


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


  • system


该表只有一行(=system 表)。 这是const连接类型的特例。



  • 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 and 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

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



  • unique_subquery

这种类型替换了以下形式的一些IN子查询的eq_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_len包含使用的最长密钥部分。 这个类型的ref列是NULL。


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



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

索引连接类型与ALL相同,只是索引树被扫描。 这发生在两个方面:


    • 如果索引是查询的覆盖索引,并且可用于满足表中所需的所有数据,则只扫描索引树。 在这种情况下,额外列说使用索引。 仅索引扫描通常比ALL快,因为索引的大小通常小于表数据。
    • 全表扫描使用索引中的读取来按索引顺序查找数据行。 使用索引不会出现在Extra列中。
  • ALL

全表扫描是针对先前表中的每个行的组合完成的。 如果表是没有标记为const的第一个表,通常情况下不好。 通常情况下,您可以通过添加索引来避免ALL,这些索引可以根据来自较早表的常量值或列值从表中检索行。




EXPLAIN Extra 信息


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


如果要尽可能快地查询,请查找使用filesort和Using temporary的Extra列值,或者在JSON格式的EXPLAIN输出中查找等于true的using_filesort和using_temporary_table属性。



  • Child of 'table' pushed join@1 (JSON:消息文本)


该表被引用为可以下推到NDB内核的联接中的表的子表。 仅当启用了下推连接时才适用于NDB群集。 有关更多信息和示例,请参阅ndb_join_pushdown服务器系统变量的说明。


  • const row not found(JSON属性:const_row_not_found)


对于SELECT ... FROM tbl_name这样的查询,表是空的。


  • Deleting all rows(JSON属性:消息)


对于DELETE,一些存储引擎(如MyISAM)支持一种处理器方法,以简单快捷的方式删除所有表格行。 如果引擎使用此优化,则会显示此额外值。


  • Distinct(JSON 属性:distinct)


MySQL正在寻找不同的值,所以当它找到第一个匹配的行后,它会停止为当前行组合搜索更多的行。



  • FirstMatch(tbl_name)(JSON 属性 first_match)


tbl_name使用半连接的FirstMatch连接快捷方式策略。



  • Full scan on NULL key (JSON属性:message)


当优化程序不能使用索引查找访问方法时,会发生子查询优化作为回退策略。



  • Impossible HAVING (JSON 属性: message)


HAVING子句总是为false,不能选择任何行。



  • Impossible WHERE (JSON 属性: message)


WHERE子句总是为false,不能选择任何行。



  • Impossible WHERE noticed after reading const tables (JSON property: message)


MySQL已经读取了所有的const(和system)表,并注意到WHERE子句总是为false。


  • LooseScan(m..n) (JSON property: message)


使用半连接LooseScan策略。 m和n是key part编号。


  • No matching min/max row (JSON property: message)


有行满足查询的条件,如SELECT MIN(...)FROM ... WHERE条件。


  • no matching row in const table (JSON property: message)


对于具有连接的查询,有一个空表或一个没有满足唯一索引条件的行的表。


  • No matching rows after partition pruning (JSON property: message)


对于DELETE或UPDATE,优化器在分区修剪后没有发现任何要删除或更新的内容。 它的含义与 Impossible WHERE for SELECT语句类似。


  • No tables used (JSON property: message)


查询没有FROM子句,或者有FROM DUAL子句。

对于INSERT或REPLACE语句,当没有SELECT部分时,EXPLAIN显示这个值。 例如,它出现在EXPLAIN INSERT INTO t VALUES(10)中,因为它相当于EXPLAIN INSERT INTO t SELECT 10 FROM DUAL。


  • Not exists (JSON property: message)


MySQL能够对查询执行LEFT JOIN优化,并且在找到与LEFT JOIN条件匹配的一行之后,不会在该表中检查前一行组合的更多行。 以下是可以用这种方式进行优化的查询类型的示例:


SELECT * FROM t1 LEFT JOIN t2 ON t1.id=t2.id WHERE t2.id IS NULL;

假定t2.id被定义为NOT NULL。 在这种情况下,MySQL扫描t1并使用t1.id的值查找t2中的行。 如果MySQL在t2中找到匹配的行,它知道t2.id不能为NULL,并且不会扫描t2中具有相同id值的其余行。 换句话说,对于t1中的每一行,MySQL只需要在t2中进行一次查找,而不管t2中有多少行实际匹配。



  • Plan isn't ready yet (JSON property: none)


当优化程序还没有完成在指定连接中执行的语句的执行计划时,此值将与EXPLAIN FOR CONNECTION一起出现。 如果执行计划输出包含多行,则根据优化程序在确定完整执行计划时的进度,它们中的任何一个或全部可以具有此额外值。



  • Range checked for each record (index map: N) (JSON property: message)


MySQL发现没有好的索引来使用,但发现一些索引可能在前面表格的列值已知之后使用。 对于上表中的每一行组合,MySQL检查是否可以使用range或index_merge访问方法来检索行。 这不是最快的,但比完成没有索引的连接要快。 适用性标准如第8.2.1.2节“范围优化”和第8.2.1.3节“索引合并优化”中所述,不同之处在于前表的所有列值是已知的并被认为是常数。


索引从1开始编号,其顺序与表中SHOW INDEX所示的顺序相同。 索引映射值N是指示哪些索引是候选的位掩码值。 例如,值0x19(二进制11001)意味着将考虑索引1,4和5。


  • Scanned N databases (JSON property: message)


这表示在处理对INFORMATION_SCHEMA表的查询时,服务器执行多少个目录扫描,如第8.2.3节“优化INFORMATION_SCHEMA查询”中所述。 N的值可以是0,1或all。


  • Select tables optimized away (JSON property: message)


优化器确定1)至多应该返回一行,并且2)为了产生该行,必须读取确定性的一组行。 当在优化阶段读取的行可以被读取(例如通过读取索引行)时,在查询执行期间不需要读取任何表。


当查询被隐式分组(包含一个聚合函数但没有GROUP BY子句)时,满足第一个条件。 当使用每个索引执行一个行查找时,满足第二个条件。 读取的索引数决定了要读取的行数。

考虑下面的隐式分组查询:

SELECT MIN(c1), MIN(c2) FROM t1;

假设可以通过读取一个索引行来检索MIN(c1),并且可以通过从不同索引读取一行来检索MIN(c2)。 也就是说,对于每个列c1和c2,都存在一个索引,其中列是索引的第一列。 在这种情况下,返回一行,通过读取两个确定性行产生。

如果要读取的行不是确定性的,则不会出现此Extra值。 考虑这个查询:

SELECT MIN(c2) FROM t1 WHERE c1 <= 10;

假设(c1,c2)是覆盖索引。 使用此索引,必须扫描c1 <= 10的所有行以查找最小c2值。 相比之下,考虑这个查询:

SELECT MIN(c2) FROM t1 WHERE c1 = 10;

在这种情况下,c1 = 10的第一个索引行包含最小c2值。 只有一行必须被读取以产生返回的行。

对于每个表保持准确行数的存储引擎(例如MyISAM,而不是InnoDB),对于COUNT(*)查询,WHERE子句缺失或始终为真且没有GROUP BY子句时,可能会发生此Extra值。 (这是隐式分组查询的一个实例,存储引擎影响是否可以读取确定数量的行。


  • Skip_open_table, Open_frm_only, Open_full_table (JSON property: message)


这些值表示适用于对INFORMATION_SCHEMA表的查询的文件打开优化,如第8.2.3节“优化INFORMATION_SCHEMA查询”中所述。



    • Skip_open_table:表文件不需要打开。 通过扫描数据库目录,查询中的信息已经可用。
    • Open_frm_only:只需要打开表格的.frm文件。
    • Open_full_table:未优化的信息查找。 必须打开.frm,.MYD和.MYI文件。
  • Start temporary, End temporary (JSON property: message)


这表示半连接Duplicate Weedout策略的临时表使用情况。


  • unique row not found (JSON property: message)


对于像SELECT ... FROM tbl_name这样的查询,没有行满足表中UNIQUE索引或PRIMARY KEY的条件。


  • Using filesort (JSON property: using_filesort)


MySQL必须做一个额外的传递才能找出如何按排序顺序检索行。 排序是按照连接类型遍历所有行,并为与WHERE子句匹配的所有行存储排序键和指向行的指针。 然后对键进行排序,并按排序顺序检索行。



  • Using index (JSON property: using_index)


只使用索引树中的信息从表中检索列信息,而不必执行额外的查找来读取实际行。 当查询仅使用属于单个索引一部分的列时,可以使用此策略。


对于具有用户定义的聚簇索引的InnoDB表,即使Extra列中缺少使用索引,也可以使用该索引。 如果type是索引而key是PRIMARY,则是这种情况。



  • Using index condition (JSON property: using_index_condition)


通过访问索引元组来读取表,并首先测试它们以确定是否读取整个表的行。 这样,除非必要,否则索引信息用于推迟(“ push down”)读取全表行。



  • Using index for group-by (JSON property: using_index_for_group_by)


与使用索引表访问方法类似,对group-by使用index指示MySQL找到了一个索引,可用于检索GROUP BY或DISTINCT查询的所有列,而无需对实际表进行任何额外的磁盘访问。 此外,索引以最有效的方式使用,因此对于每个组,只有少数索引条目被读取。



  • Using join buffer (Block Nested Loop), Using join buffer (Batched Key Access) (JSON property: using_join_buffer)


先前连接的表被分成几部分读入连接缓冲区,然后从缓冲区中使用它们的行来执行与当前表的连接。 ( Block Nested Loop)指示使用块嵌套循环算法,并且( Batched Key Access)指示使用批次密钥访问算法。 也就是说,EXPLAIN输出的前一行表中的键将被缓冲,匹配的行将从使用加入缓冲区出现的行表示的表中批量获取。

在JSON格式的输出中,using_join_buffer的值始终是Block Nested Loop或Batched Key Access中的一个。



  • Using MRR (JSON property: message)


使用多范围读取优化策略读取表格。



  • Using sort_union(...), Using union(...), Using intersect(...) (JSON property: message)


这些表明特定的算法显示索引扫描如何合并index_merge连接类型。


  • Using temporary (JSON property: using_temporary_table)


为了解决这个查询,MySQL需要创建一个临时表来保存结果。 如果查询包含以不同方式列出列的GROUP BY和ORDER BY子句,则通常会发生这种情况。


  • Using where (JSON property: attached_condition)


WHERE子句用于限制哪些行与下一个表匹配或发送给客户端。 除非您专门打算从表中读取或检查所有行,否则如果Extra值不是使用where和表连接类型是ALL或索引,则查询中可能有问题。


使用JSON格式的输出中没有直接对应的地方; attached_condition属性包含使用的任何WHERE条件。



  • Using where with pushed condition (JSON property: message)


此项目仅适用于NDB表格。 这意味着NDB集群正在使用条件下推优化来提高直接比较非索引列和常量的效率。 在这种情况下,条件被“推下”到集群的数据节点,并在所有数据节点上同时进行评估。 这消除了通过网络发送不匹配的行的需要,并且在情况下推可能但未被使用的情况下,可以将这些查询加速5到10倍。


  • Zero limit (JSON property: message)


查询有一个LIMIT 0子句,不能选择任何行。



EXPLAIN 输出解释


您可以通过使用EXPLAIN输出的rows列中的值的乘积来很好地指示连接有多好。 这应该大致告诉你MySQL必须检查多少行以执行查询。 如果使用max_join_size系统变量限制查询,则该行产品还用于确定要执行哪个多表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 Data 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语句会生成以下信息:





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



这里的一个问题是,如果MySQL声明为相同的类型和大小,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语句会产生这样的结果:




这并不完美,但要好得多:行值的乘积减少了74倍。该版本在几秒钟内执行。


可以进行第二次更改以消除tt.AssignedPC = et_1.EMPLOYID和tt.ClientID = do.CUSTNMBR比较的列长度不匹配:


ALTER TABLE tt MODIFY AssignedPC VARCHAR(15), MODIFY ClientID VARCHAR(15);


修改之后,EXPLAIN生成如下所示的输出:




在这一点上,查询几乎尽可能地被优化。 剩下的问题是,默认情况下,MySQL假定tt.ActualPC列中的值是均匀分布的,tt表不是这种情况。 幸运的是,很容易告诉MySQL分析key 分布:


mysql> ANALYZE TABLE tt;



有了额外的索引信息,连接是完美的,EXPLAIN产生这样的结果:





EXPLAIN输出中的rows列是来自MySQL连接优化器的有根据的猜测。 通过将行产品与查询返回的实际行数进行比较来检查数字是否接近真相。 如果数字非常不同,则可以在SELECT语句中使用STRAIGHT_JOIN并尝试在FROM子句中以不同的顺序列出表。 (但是,STRAIGHT_JOIN可能会阻止索引被使用,因为它会禁用半连接转换。


当EXPLAIN SELECT与子查询一起使用时,可能在某些情况下执行修改数据的语句;




8.8.3 拓展 EXPLAN 输出格式


对于SELECT语句,EXPLAIN语句会生成不属于EXPLAIN输出的额外(“扩展”)信息,但可以通过在EXPLAIN之后发出SHOW WARNINGS语句来查看。 SHOW WARNINGS输出中的Message值显示了优化程序如何限定SELECT语句中的表名和列名,在应用重写和优化规则之后SELECT的样子,以及可能有关优化过程的其他说明。


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


注意:

在以前的MySQL版本中,扩展信息是使用EXPLAIN EXTENDED生成的。 该语法仍被认为是向后兼容的,但扩展输出现在默认是启用的,所以EXTENDED关键字是多余的,并且被弃用。 它的使用会导致一个警告,并且在未来的MySQL版本中它将被从EXPLAIN语法中删除。




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


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


  • <auto_key>

一个临时表的自动生成的key。


  • <cache>(expr)


表达式(例如标量子查询)被执行一次,结果值被保存在内存中供以后使用。 对于由多个值组成的结果,可能会创建一个临时表,您将看到<temporary table>。


  • <exists>(query fragment)


子查询谓词转换为EXISTS谓词,并且子查询被转换,以便可以与EXISTS谓词一起使用。


  • <in_optimizer>(query fragment)


这是一个没有用户意义的内部优化器对象。


  • <index_lookup>(query fragment)


查询片段使用索引查找来处理,以查找符合条件的行。


  • <if>(condition, expr1, expr2)


如果条件为真,评估为expr1,否则为expr2。


  • <is_not_null_test>(expr)


验证表达式不计算为NULL的测试。


  • <materialize>(query fragment)


使用子查询实现。


  • `materialized-subquery`.col_name


对内部临时表中的列col_name的引用,这些内容被物化以保留评估子查询的结果。


  • <primary_index_lookup>(query fragment)


使用主键查找处理查询片段以查找合格的行。


  • <ref_null_helper>(expr)


这是一个没有用户意义的内部优化器对象。


  • /* select#N */ select_stmt


SELECT与非扩展EXPLAIN输出中具有id值为N的行关联


  • outer_tables semi join (inner_tables)


半连接操作。 inner_tables显示没有被拉出的表。


  • <temporary table>


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


当某些表是常量或系统类型时,涉及这些表的列的表达式将在优化器的早期进行评估,而不是显示的语句的一部分。 但是,对于FORMAT = JSON,某些const表访问会显示为使用const值的ref访问。



8.8.4 获取命名连接的执行计划信息


要获取在命名连接中执行的可解释语句的执行计划,请使用以下语句:

EXPLAIN [options] FOR CONNECTION connection_id;


EXPLAIN FOR CONNECTION返回当前用于在给定连接中执行查询的EXPLAIN信息。 由于数据(和支持统计数据)的更改,它可能会在等效查询文本上运行EXPLAIN时产生不同的结果。 这种行为差异可以用于诊断更多的暂时性能问题。 例如,如果在一个需要很长时间才能完成的会话中运行语句,则在另一个会话中使用EXPLAIN FOR CONNECTION可能会产生有关延迟原因的有用信息。


connection_id是从INFORMATION_SCHEMA PROCESSLIST表或SHOW PROCESSLIST语句获得的连接标识符。 如果您拥有PROCESS权限,则可以为任何连接指定标识符。 否则,您只能为自己的连接指定标识符。


如果指定的连接没有执行语句,则结果为空。 否则,EXPLAIN FOR CONNECTION只适用于在指定连接中执行的语句是可解释的。 这包括SELECT,DELETE,INSERT,REPLACE和UPDATE。 (但是,EXPLAIN FOR CONNECTION不适用于准备好的语句,甚至无法准备好这些类型的语句。)


如果指定的连接正在执行一个可解释的语句,那么输出就是你在语句本身上使用EXPLAIN获得的结果。


如果指定的连接正在执行不可解释的语句,则会发生错误。 例如,您不能为当前会话命名连接标识符,因为EXPLAIN不可解释:

mysql> SELECT CONNECTION_ID();

+-----------------+

| CONNECTION_ID() |

+-----------------+

| 373 |

+-----------------+

1 row in set (0.00 sec)


mysql> EXPLAIN FOR CONNECTION 373;

ERROR 1889 (HY000): EXPLAIN FOR CONNECTION command is supported

only for SELECT/UPDATE/INSERT/DELETE/REPLACE


Com_explain_other状态变量指示执行的EXPLAIN FOR CONNECTION语句的数量。


--类似会话trace



8.8.5 评估查询性能


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


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


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


然而,对于写操作,需要四个seek请求来查找放置新索引值的位置,通常需要两次查找来更新索引并写入行。


前面的讨论并不意味着你的应用程序的性能会以log N慢慢退化。只要所有的东西都被操作系统或者MySQL服务器缓存,事物变得越来越慢。 在数据变得太大而无法缓存之后,事情开始慢得多,直到你的应用程序只能被磁盘查找(由log N增加)所限制。 为避免这种情况,请在数据增长时增加key缓存大小。 对于MyISAM表,密钥缓存大小由key_buffer_size系统变量控制。

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69946831/viewspace-3168155/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2019-09-09

  • 博文量
    2646
  • 访问量
    42201