美文网首页服务器后端开发Java数据库
explain的属性详解与提速百倍的优化示例

explain的属性详解与提速百倍的优化示例

作者: 全菜工程师小辉 | 来源:发表于2019-08-18 13:57 被阅读17次

    在MySQL中,可以通过EXPLAIN命令获取MySQL如何执行SELECT语句的信息,包括在SELECT语句执行过程中表如何连接和连接的顺序。

    EXPLAIN命令虽然没有提供任何优化建议,但它能够提供重要的信息有助于调优决策。

    EXPLAIN只能解释SELECT操作,其他操作要重写为SELECT后查看执行计划。

    使用方法

    在要查询的SQL语句前加上explain,然后执行就可以了。如:

    EXPLAIN SELECT
        goods_name,
        seckill_price
    FROM
        seckill_goods,
        goods
    WHERE
        seckill_goods.id = goods.id
    

    explain属性的含义

    执行上面SQL语句之后。

    explain

    各属性含义:(笔者常关注的参数:type、key、rows)

    id

    查询的序列号。

    id是一组数字,表示查询中执行select子句或操作表的顺序;如果id相同,则执行顺序从上至下,如果是子查询,id的序号会递增,id越大则优先级越高,越先会被执行。

    id列为null则表示这一行是一个结果集,不需要使用它来进行查询。

    select_type

    显示每个select子句的查询类型。

    • simple:表示不需要union操作或者不包含子查询的简单select查询。有连接查询时,外层的查询为simple,且只有一个。
    • primary:一个需要union操作或者含有子查询的select,位于最外层的单位查询的select_type即为primary。且只有一个。
    • union:union连接的两个select查询,第一个查询是dervied派生表,除了第一个表外,第二个以后的表select_type都是union。
    • dependent union:与union一样,出现在union 或union all语句中,但是这个查询要受到外部查询的影响。
    • union result:包含union的结果集,在union和union all语句中,因为它不需要参与查询,所以id字段为null。
    • subquery:除了from字句中包含的子查询外,其他地方出现的子查询都可能是subquery。
    • dependent subquery:与dependent union类似,表示这个subquery的查询要受到外部表查询的影响。
    • derived:from字句中出现的子查询,也叫做派生表,其他数据库中可能叫做内联视图或嵌套select。

    table

    输出的行所引用的表。

    • 显示的查询表名,如果查询使用了别名,那么这里显示的是别名。
    • 如果不涉及对数据表的操作,那么这显示为null。
    • 如果显示为尖括号括起来的<derived N>就表示这个是临时表,后边的N就是执行计划中的id,表示结果来自于这个查询产生。
    • 如果是尖括号括起来的<union M,N>,与<derived N>类似,也是一个临时表,表示这个结果来自于union查询的id为M,N的结果集。

    partitions

    版本5.7以前,该项是explain partitions显示的选项,5.7以后成为了默认选项。该列显示的是分区表命中的分区情况。非分区表该字段为空(null)。

    type

    对表访问方式,表示MySQL在表中找到所需行的方式,又称“访问类型”。

    性能依次由好到差:system,const,eq_ref,ref,fulltext,ref_or_null,unique_subquery,index_subquery,range,index_merge,index,all。

    除了all之外,其他的type都可以使用到索引。除了index_merge之外,其他的type只可以用到一个索引。

    • system:表中只有一行数据或者是空表,且只能用于myisam和memory表。
    • const:查找主键索引,返回的数据至多一条(0或者1条)。 属于精确查找。
    • eq_ref:查找唯一性索引,返回的数据至多一条。属于精确查找。
    • ref:查找非唯一性索引,返回匹配某一条件的多条数据。属于精确查找、数据返回可能是多条。
    • fulltext:全文索引检索,要注意,全文索引的优先级很高,若全文索引和普通索引同时存在时,mysql不管代价,优先选择使用全文索引。
    • ref_or_null:与ref方法类似,只是增加了null值的比较。实际用的不多。
    • unique_subquery:用于where中的in形式子查询,子查询返回不重复值唯一值。
    • index_subquery:用于in形式子查询使用到了辅助索引或者in常数列表,子查询可能返回重复值,可以使用索引将子查询去重。
    • range:索引范围扫描,常见于使用>,<,is null,between ,in ,like等运算符的查询中。
    • index_merge:表示查询使用了两个以上的索引,最后取交集或者并集,常见and ,or的条件使用了不同的索引,官方排序这个在ref_or_null之后,但是实际上由于要读取所个索引,性能可能都不如range。
    • index:索引全表扫描,把索引从头到尾扫一遍,常见于使用索引列就可以处理不需要读取数据文件的查询、可以使用索引排序或者分组的查询。
    • all:不使用任何索引,进行全表扫描,性能最差。

    possible_keys

    显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。

    该列完全独立于EXPLAIN输出所示的表的次序。这意味着在possible_keys中的某些键实际上不能按生成的表次序使用。

    如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查WHERE子句是否引用某些列或适合索引的列来提高查询性能

    key

    显示MySQL实际决定使用的键(索引),必然包含在possible_keys中,如果没有索引被选择,是NULL。

    type为index_merge时,这里可能出现两个以上的索引,其他的type这里只会出现一个。

    key_len

    使用到索引字段的长度。

    如果是单列索引,那就返回整个索引长度;如果是多列索引,那么查询不一定都能使用到所有的列,返回具体使用索引的长度(没有使用到的列,这里不会计算进去)。对比这个数值和多列索引的总长度,就可以推测是否使用到所有的列。

    mysql的ICP特性使用到的索引不会计入其中。

    key_len只计算where条件用到的索引长度,而排序和分组就算用到了索引,也不会计算到key_len中。

    key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的。

    ref

    显示索引的那一列被使用了,如果可能的话,最好是一个常数。哪些列或常量被用于查找索引列上的值。

    rows

    MySQL根据表统计信息及索引选用情况,估算mysql查询过程中遍历的行数,不是准确值。

    filtered

    使用explain extended时会出现这个列,5.7之后的版本默认就有这个字段,不需要使用explain extended了。这个字段表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,这个值是百分比,不是具体记录数。

    Extra

    执行情况的说明和描述,显示信息种类非常多,下面只列举常见的结果。

    • distinct:在select部分使用了distinct关键字
    • no tables used:不带from字句的查询或者From dual查询。
      使用not in()形式子查询或not exists运算符的连接查询,这种叫做反连接。即,一般连接查询是先查询内表,再查询外表,反连接就是先查询外表,再查询内表。
    • using filesort:排序时无法使用到索引时,就会出现这个。常见于order by和group by语句中。
    • using index:查询时不需要回表查询,直接通过索引就可以获取查询的数据。
    • using_union:表示使用or连接各个使用索引的条件时,该信息表示从处理结果获取并集
    • using intersect:表示使用and的各个索引的条件时,该信息表示是从处理结果获取交集
    • using sort_union和using sort_intersection:与前面两个对应的类似,只是他们是出现在用and和or查询信息量大时,先查询主键,然后进行排序合并后,才能读取记录并返回。
    • using where:表示存储引擎返回的记录并不是所有的都满足查询条件,需要在server层进行过滤。查询条件中分为限制条件和检查条件,5.6之前,存储引擎只能根据限制条件扫描数据并返回,然后server层根据检查条件进行过滤再返回真正符合查询的数据。5.6.x之后支持ICP特性,可以把检查条件也下推到存储引擎层,不符合检查条件和限制条件的数据,直接不读取,这样就大大减少了存储引擎扫描的记录数量。extra列显示using index condition
    • using temporary:表示使用了临时表存储中间结果,通常是因为GROUP BY的列没有索引,或者GROUP BY和ORDER BY的列不一样,也需要创建临时表,建议添加适当的索引。临时表可以是内存临时表和磁盘临时表,执行计划中看不出来,需要查看status变量,used_tmp_table,used_tmp_disk_table才能看出来。
    • firstmatch(tb_name):5.6.x开始引入的优化子查询的新特性之一,常见于where字句含有in()类型的子查询。如果内表的数据量比较大,就可能出现这个
    • loosescan(m..n):5.6.x之后引入的优化子查询的新特性之一,在in()类型的子查询中,子查询返回的可能有重复记录时,就可能出现这个
    • filtered:使用explain extended时会出现这个列,5.7之后的版本默认就有这个字段,不需要使用explain extended了。这个字段表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,注意是百分比,不是具体记录数。

    优化示例

    优化示例章节,节选“美团技术团队”的“MySQL索引原理及慢查询优化”文章,点击查看(如果链接失效,请查看原文)

    慢查询优化基本步骤:

    1. 先运行看看是否真的很慢,注意设置SQL_NO_CACHE
    2. where条件单表查,锁定最小返回记录表——把查询语句的where都应用到表中返回的记录数最小的表开始查起,单表每个字段分别查询,看哪个字段的区分度最高
    3. explain查看执行计划,是否从锁定记录较少的表开始查询。
    4. order by limit 形式的sql语句让排序的表优先查
    5. 了解业务方使用场景
    6. 加索引时参照建索引的几大原则
    7. 观察结果,不符合预期继续从0分析

    不同的SQL语句书写方式对于效率往往有本质的差别,这要求我们对mysql的执行计划和索引原则有非常清楚的认识,请看下面的语句:

    select
       distinct cert.emp_id 
    from
       cm_log cl 
    inner join
       (
          select
             emp.id as emp_id,
             emp_cert.id as cert_id 
          from
             employee emp 
          left join
             emp_certificate emp_cert 
                on emp.id = emp_cert.emp_id 
          where
             emp.is_deleted=0
       ) cert 
          on (
             cl.ref_table='Employee' 
             and cl.ref_oid= cert.emp_id
          ) 
          or (
             cl.ref_table='EmpCertificate' 
             and cl.ref_oid= cert.cert_id
          ) 
    where
       cl.last_upd_date >='2013-11-07 15:03:00' 
       and cl.last_upd_date<='2013-11-08 16:00:00';
    

    1.直接运行尝试

    先运行一下,53条记录 1.87秒,又没有用聚合语句,比较慢

    53 rows in set (1.87 sec)

    2.执行explain查看执行计划

    +----+-------------+------------+-------+---------------------------------+-----------------------+---------+-------------------+-------+--------------------------------+
    | id | select_type | table      | type  | possible_keys                   | key                   | key_len | ref               | rows  | Extra                          |
    +----+-------------+------------+-------+---------------------------------+-----------------------+---------+-------------------+-------+--------------------------------+
    |  1 | PRIMARY     | cl         | range | cm_log_cls_id,idx_last_upd_date | idx_last_upd_date     | 8       | NULL              |   379 | Using where; Using temporary   |
    |  1 | PRIMARY     | <derived2> | ALL   | NULL                            | NULL                  | NULL    | NULL              | 63727 | Using where; Using join buffer |
    |  2 | DERIVED     | emp        | ALL   | NULL                            | NULL                  | NULL    | NULL              | 13317 | Using where                    |
    |  2 | DERIVED     | emp_cert   | ref   | emp_certificate_empid           | emp_certificate_empid | 4       | meituanorg.emp.id |     1 | Using index                    |
    +----+-------------+------------+-------+---------------------------------+-----------------------+---------+-------------------+-------+--------------------------------+
    

    简述一下执行计划,首先mysql根据idx_last_upd_date索引扫描cm_log表获得379条记录;然后查表扫描了63727条记录,分为两部分,derived表示构造表,也就是不存在的表,可以简单理解成是一个语句形成的结果集,后面的数字表示语句的ID。derived2表示的是ID = 2的查询构造了虚拟表,并且返回了63727条记录。我们再来看看ID = 2的语句究竟做了写什么返回了这么大量的数据,首先全表扫描employee表13317条记录,然后根据索引emp_certificate_empid关联emp_certificate表,rows = 1表示,每个关联都只锁定了一条记录,效率比较高。获得后,再和cm_log的379条记录根据规则关联。从执行过程上可以看出返回了太多的数据,返回的数据绝大部分cm_log都用不到,因为cm_log只锁定了379条记录。

    3.优化分析

    如何优化呢?可以看到在运行完后还是要和cm_log做join,那么我们能不能之前和cm_log做join呢?仔细分析语句不难发现,其基本思想是如果cm_log的ref_table是EmpCertificate就关联emp_certificate表,如果ref_table是Employee就关联employee表,我们完全可以拆成两部分,并用union连接起来,注意这里用union,而不用union all是因为原语句有“distinct”来得到唯一的记录,而union恰好具备了这种功能。如果原语句中没有distinct不需要去重,就可以直接使用union all了,因为使用union需要去重的动作,会影响SQL性能。

    优化过的语句如下:

    select
       emp.id 
    from
       cm_log cl 
    inner join
       employee emp 
          on cl.ref_table = 'Employee' 
          and cl.ref_oid = emp.id  
    where
       cl.last_upd_date >='2013-11-07 15:03:00' 
       and cl.last_upd_date<='2013-11-08 16:00:00' 
       and emp.is_deleted = 0  
    union
    select
       emp.id 
    from
       cm_log cl 
    inner join
       emp_certificate ec 
          on cl.ref_table = 'EmpCertificate' 
          and cl.ref_oid = ec.id  
    inner join
       employee emp 
          on emp.id = ec.emp_id  
    where
       cl.last_upd_date >='2013-11-07 15:03:00' 
       and cl.last_upd_date<='2013-11-08 16:00:00' 
       and emp.is_deleted = 0
    

    4.确保优化后的结果与之前结果一致

    本次优化不需要了解业务场景,只需要改造的语句和改造之前的语句保持结果一致

    5.判断是否加索引

    现有索引可以满足,不需要建索引

    6.观察优化结果

    用改造后的语句实验一下,只需要10ms,降低了近200倍!

    +----+--------------+------------+--------+---------------------------------+-------------------+---------+-----------------------+------+-------------+
    | id | select_type  | table      | type   | possible_keys                   | key               | key_len | ref                   | rows | Extra       |
    +----+--------------+------------+--------+---------------------------------+-------------------+---------+-----------------------+------+-------------+
    |  1 | PRIMARY      | cl         | range  | cm_log_cls_id,idx_last_upd_date | idx_last_upd_date | 8       | NULL                  |  379 | Using where |
    |  1 | PRIMARY      | emp        | eq_ref | PRIMARY                         | PRIMARY           | 4       | meituanorg.cl.ref_oid |    1 | Using where |
    |  2 | UNION        | cl         | range  | cm_log_cls_id,idx_last_upd_date | idx_last_upd_date | 8       | NULL                  |  379 | Using where |
    |  2 | UNION        | ec         | eq_ref | PRIMARY,emp_certificate_empid   | PRIMARY           | 4       | meituanorg.cl.ref_oid |    1 |             |
    |  2 | UNION        | emp        | eq_ref | PRIMARY                         | PRIMARY           | 4       | meituanorg.ec.emp_id  |    1 | Using where |
    | NULL | UNION RESULT | <union1,2> | ALL    | NULL                            | NULL              | NULL    | NULL                  | NULL |             |
    +----+--------------+------------+--------+---------------------------------+-------------------+---------+-----------------------+------+-------------+
    53 rows in set (0.01 sec)
    
    哎呀,如果我的名片丢了。微信搜索“全菜工程师小辉”,依然可以找到我

    相关文章

      网友评论

        本文标题:explain的属性详解与提速百倍的优化示例

        本文链接:https://www.haomeiwen.com/subject/ibursctx.html