当前位置: 首页 > 科技观察

你能读懂MySQL的执行计划(EXPLAIN)吗?

时间:2023-03-22 10:47:27 科技观察

SQL执行太慢?我们通常使用EXPLAIN命令查看SQL执行计划,然后根据执行计划找出问题并进行优化。使用介绍EXPLAIN的使用非常简单,只需要在你的SQL前面加上EXPLAIN即可。例如:解释select*fromt;PS:insert、update、delete也可以通过explain查看执行计划,但通常我们更关心select的执行,会看到如下输出:+----+-------------+--------+------------+------+--------------+------+--------+------+------+--------+-------+|编号|选择类型|表|分区|类型|可能的键|钥匙|密钥长度|参考|行|过滤|额外|+----+------------+-------+------------+------+----------------+------+---------+------+------+----------+--------+|1|简单|t1|空|所有|空|空|空|空|1|100.00|空|+----+------------+--------+-----------+------+------------+------+--------+-------+------+----------+--------+1rowinset,1warning(0.00sec)执行计划的结果字段描述如下表:EXPLAIN的用法很简单,看一下它。但是要根据输出结果找到问题并解决问题就没那么容易了。就像操作一台CT机,可能比较简单,但是需要丰富的知识和大量的临床经验,才能从CT影像上看到问题,给出治疗方案。因此,我们需要知道每个字段代表什么指标;我们想要什么价值,什么需要优化;最后,我们需要知道如何将它优化到我们想要的值。字段详细信息id标识符。查询操作的序号。通常为正整数,但在有UNION操作时可以为NULL。id相同解释select*fromt1wheret1.idin(selectt2.idfromt2);+----+------------+--------+------------+--------+--------------+--------+|编号|选择类型|表|分区|类型|可能的键|...|+----+------------+--------+------------+--------+--------------+--------+|1|简单|t1|空|所有|初级|....||1|简单|t2|空|eq_ref|初级|....|+----+------------+--------+------------+--------+--------------+--------+2rowsinset,1warning(0.00sec)2rowsinset,1warning(0.00sec)id不同解释select*fromt1wheret1.id=(selectt2.idfromt2);+----+------------+--------+------------+--------+---------------+--------+|编号|选择类型|表|分区|类型|可能的键|...|+----+------------+--------+------------+--------+---------------+--------+|1|初级|空|空|空|空|....||2|子查询|t2|空|索引|空|....|+----+------------+--------+-----------+--------+---------------+--------+集合中的2行,1个警告(0.00秒)id包含NULLexplainselectidfromt1union(selectidfromt2);+----+------------+-----------+------------+-------+----------------+----------+|编号|选择类型|表|分区|类型|可能的键|...|+------+------------+-------------+------------+-------+----------------+----------+|1|初级|t1|空|索引|空|...||2|联盟|t2||所有|空|...|+------+------------+------------+-----------+-------+----------------+--------+set中的3行,1个警告(0.00秒)当id为NULL时,表columnvalue是格式,表示行为id是m和n并集的结果id顺序的规则:如果id同理,执行顺序是从上到下;如果不同,执行顺序是从大到小select_typeSELECT类型,常用值如下:UNION或子查询MySQL会自动生成一个临时表。派生表可以简单的理解为带有别名的临时表。生成临时表的动作称为物化(水变成蒸汽称为汽化)。临时表通常在内存中,当它的大小超过一定范围时,就会存储在磁盘中。#临时表select*fromt1joint2ont1.id=t2.idwheret1.id>1;#派生表,临时表取别名select*from(select*fromt1)t;type连接字段为主键或唯一索引,这种类型通常出现在多表的连接查询中,表示对于每一个结果在前一张表,它对应的是后一张表中的唯一结果。而且查询的比较是=操作,查询效率比较高。还有一种NULL的情况,比如selectmin(id)fromt1,但是MySQL官方没有提到这种情况,所以这里不讨论性能。从好到坏的顺序是:system>const>eq_ref>ref>fulltext>ref_or_null>index_merge>unique_subquery>index_subquery>range>index>ALL优化原则:最好是const,至少是ref,避免使用的类型与ALLref查询中的索引进行比较,如:id=1,值为const;如果是联合查询或子查询是关联字段;如果使用函数,则为func。ExtraExtra是用来存放一些附加信息的,通常用type的输出来做SQL优化。扩展的descdesc与explain的功能相同,可以相互替代。在下面的例子中,desc用于查看执行计划。formatexplain/desc也支持一些参数,format顾名思义就是用来格式化输出结果的。它包括两种格式化方法:tree和json。例如:descformat=treeselect*fromt1wheret1.idin(selectt2.idfromt2wheret2.id>1);输出格式如下:+------------------------------------------------------------------------------+|解释|+--------------------------------------------------------------------------------+|->嵌套循环内连接(cost=0.70rows=1)->Filter:(t2.id>1)(cost=0.35rows=1)->使用a2_uidx在t2上进行索引扫描(cost=0.35rows=1)->使用PRIMARY(id=t2.id)(cost=0.35rows=1)在t1上进行单行索引查找|+--------------------------------------------------------------------------------+1rowinset(0.00sec)执行计划结果显示在一个树形结构,可以清楚地看到语句之间的嵌套关系,以及基本的执行成本(cost)。使用json方法:descformat=jsonselect*fromt1;输出结构是一个JSON结构:+------------------------------------------------+|解释|+--------------------------------------------------+|{“query_block”:{“select_id”:1,“cost_info”:{“query_cost”:“0.35”},“table”:{“table_name”:“t1”,“access_type”:“ALL”,“rows_examined_per_scan”:1,“rows_produced_per_join”:1,“filtered”:“100.00”,“cost_info”:{“read_cost”:“0.25”,“eval_cost”:“0.10”,“prefix_cost”:“0.35”,“data_read_per_join”:“56”},“used_columns”:[“id”,“a1”,“b1”]}}}|+----------------------------------------------------+1rowinset,1warning(0.00sec)介绍表中的JSONName指的是这里JSON结果的keyjson格式,会显示更详细的信息,可以看到执行的划分costs比较详细,也方便定位慢SQL的问题出现在哪里。analyze除掉了格式以外,explain/desc还可以使用analyze参数:descanalyzeselect*fromt1wheret1.idin(selectt2.idfromt2wheret2.id>1);输出结果:+-------------------------------------------------------------------------------------------------+|解释|+--------------------------------------------------------------------------------------------------+|->嵌套循环内连接(cost=0.70rows=1)(actualtime=0.018..0.018rows=0loops=1)->Filter:(t2.id>1)(cost=0.35rows=1)(actualtime=0.016..0.016rows=0loops=1)->使用a2_uidx在t2上进行索引扫描(cost=0.35rows=1)(actualtime=0.015..0.015rows=0loops=1)->单行索引使用PRIMARY(id=t2.id)(cost=0.35rows=1)在t1上查找(从未执行)|+----------------------------------------------------------------------------------------------------+1rowinset(0.00sec)可以看出analyze的输出是根据上面format=tree的执行计划(format=json/tree)中的执行成本(cost)为estimated,analyze中的执行成本为真实值。actualtime表示相应SQL执行的实时时间,单位为毫秒。在最终执行计划的结果中,我们最关心的是类型,它最能直接反映SQL执行效率的高低。然后再结合其他字段(比如Extra)进行更详细的分析。还可以使用各种参数来分解每个环节的执行。