mysql sql执行计划_SQL执行计划解读

声明

5.6中desc看不到show warnings,也看不到filtered列

5.7的desc等于5.6的desc extended,这样可以看show warnings,5.6中filtered列非常不准,5.7好一些

先看一个执行计划

(root@localhost) [test]> desc select * from l;

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

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |

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

Ⅰ、展开分析每个字段

id列——表示sql执行的顺序

id相等,一般是简单关联,从上往下看即可

id不相等,一般为出现了子查询,先看大的再看小的

同时存在相等与不相等,一般相等的为一组,先看大的再看小的

有个潜规则叫:id相等从上往下看,id不等从下往上看

select_type——select类型,用于区分子查询,关联查询等

SIMPLE:简单查询,不包含子查询与union

PRIMARY:查询中包含子查询,最外层查询则被标记为PRIMARY

UNION:使用union连接select时,从第二个select开始都是UNION

SUBQUERY:select或者where后面的子查询(非from之后)都可能是SUBQUERY

DERIVED:在from中包含的子查询被标记为DERIVED(派生表)

DEPENDENTSUBQUERY:依赖外部查询的SUBQUERY

UNION RESULT:UNION的结果,对应ID为NULL

table——输出记录的表

查询中使用别名,则此处显示别名

不涉及表的操作,则显示为NULL

< derivedN > / < subqueryN > 由ID为N的查询产生的结果

< unionM,N > 由ID为M,N查询union产生的结果集

type——访问类型

system:const的特例,表中只有一行记录

const:使用唯一索引或主键,只取一行数据

eq_ref:多表join时,驱动表只返回一行数据,且这行数据是被驱动表的主键或唯一索引,且必须not null

ref:非唯一索引的扫描,通常为非唯一索引的等值查询

range:检索给定范围的行,使用一个索引来选择行,通常为where条件中出现between、、in、like等

index:full index scan,根据索引读全表

ALL:full table scan,扫整个数据文件

fulltext:全文索引

ref_or_null:使用普通索引进行查询,但要查询null值

index_merge or:查询会使用到的类型,可能一条sql使用了两个索引,然后merge

unique_subquery和index_subquery:很少出现 前一个是子查询的列是唯一索引,第二个是子查询的列是普通索引

主要优化对象是index和ALL,有两种情况可以考虑保留index

只查询索引列,不回表或者使用索引进行排序或者聚合

possible_keys

优化器可能使用到的索引

key

优化器实际选择的索引

key_len

使用索引的字节长度

ref

等值查询会显示const

连接查询的话被驱动表此处显示驱动表的join列

rows

优化器预估的记录数量

filtered

根据条件过滤得到的记录的百分比

extra

Using index:优化器只需使用索引就能得到结果 索引覆盖

Using index condition:优化器试用index condition pushdown优化,二级索引

Using index for group by:优化器只需使用索引就能处理group by 或者distinct语句

上面这3个基本上忽略吧,没什么参数好调的

Using temporary:使用临时表,常见于order by,group by

Using filesort:使用额外的排序 调整sort_buffer_size

Using join buffer:优化器需要使用join buffer 调整join_buffer_size

Using MRR:优化器使用MRR优化 调整read_cache_size

Using temporary:优化器需要使用临时表 调整tmp_table_size

Using where:优化器使用where过滤

Ⅱ、分析两个执行计划看看

案例1

(root@localhost) [dbt3]> DESC SELECT

-> *

-> FROM

-> part

-> WHERE

-> p_partkey IN (SELECT

-> l_partkey

-> FROM

-> lineitem

-> WHERE

-> l_shipdate BETWEEN ‘1997-01-01‘ AND ‘1997-02-01‘)

-> ORDER BY p_retailprice DESC

-> LIMIT 10;

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

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |

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

| 1 | SIMPLE | part | NULL | ALL | PRIMARY | NULL | NULL | NULL | 197706 | 100.00 | Using where; Using filesort |

| 1 | SIMPLE | | NULL | eq_ref | | | 5 | dbt3.part.p_partkey | 1 | 100.00 | NULL |

| 2 | MATERIALIZED | lineitem | NULL | range | i_l_shipdate,i_l_suppkey_partkey,i_l_partkey | i_l_shipdate | 4 | NULL | 138672 | 100.00 | Using index condition; Using MRR |

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

3 rows in set, 1 warning (0.01 sec)

id 顺序

1 ② part表(外表)和subquery2(id=2产生的14w记录的表)进行关联,对于part表中所有记录都要关联,一共是19w行,再和l_partkey进行关联,最后排序用到using filesort

1 ③ 内表要加索引,所以mysql优化器自动把第一步取出来的数据添加了一个唯一索引,in里面是去重的(这其实是做了一个物化),所以是唯一索引,eq_ref表示通过唯一索引进行关联,和外表中的p_partkey关联

2 ① 先查lineitem表,是一个range范围查询,使用了i_l_shipdate索引,l_shipdate是date类型,占用四个字节,预估14万行记录,过滤出百分之百,materiallized表示产生了一张实际的表,并且去添加了索引,l_partkey,唯一索引(in里面是去重的)

注意一个细节

(root@localhost) [dbt3]> DESC SELECT

-> *

-> FROM

-> part

-> WHERE

-> p_partkey IN (SELECT

-> l_partkey

-> FROM

-> lineitem

-> WHERE

-> l_shipdate BETWEEN ‘1997-01-01‘ AND ‘1997-01-07‘)

-> ORDER BY p_retailprice DESC

-> LIMIT 10;

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

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |

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

| 1 | SIMPLE | | NULL | ALL | NULL | NULL | NULL | NULL | NULL | 100.00 | Using where; Using temporary; Using filesort |

| 1 | SIMPLE | part | NULL | eq_ref | PRIMARY | PRIMARY | 4 | .l_partkey | 1 | 100.00 | NULL |

| 2 | MATERIALIZED | lineitem | NULL | range | i_l_shipdate,i_l_suppkey_partkey,i_l_partkey | i_l_shipdate | 4 | NULL | 29148 | 100.00 | Using index condition; Using MRR |

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

3 rows in set, 1 warning (0.00 sec)

驱动表就变成了subquerry2,这时候优化器又把子查询作为了外表,说明优化器很聪明

in的子查询,优化器会帮你重写成join,并且帮你选择子查询到底是内表还是外表

(root@localhost) [dbt3]> DESC select

-> a.*

-> from

-> part a,

-> (select distinct

-> l_partkey

-> from

-> lineitem

-> where l_shipdate between ‘1997-01-01‘ and ‘1997-02-01‘) b

-> where

-> a.p_partkey=b.l_partkey

-> order by a.p_retailprice desc

-> limit 10;

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

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |

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

| 1 | PRIMARY | | NULL | ALL | NULL | NULL | NULL | NULL | 138672 | 100.00 | Using where; Using temporary; Using filesort |

| 1 | PRIMARY | a | NULL | eq_ref | PRIMARY | PRIMARY | 4 | b.l_partkey | 1 | 100.00 | NULL |

| 2 | DERIVED | lineitem | NULL | range | i_l_shipdate,i_l_suppkey_partkey,i_l_partkey | i_l_shipdate | 4 | NULL | 138672 | 100.00 | Using index condition; Using MRR; Using temporary |

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

3 rows in set, 1 warning (0.00 sec)

这么改写,b表永远是外表,子查询只是产生一个派生表,但是没办法给它建索引,如果子查询出来的结果集很大,这时候性能就不如in了,in的话优化器会把它作为内表

案例2

(root@localhost) [dbt3]> DESC select max(l_extendedprice)

-> from orders,lineitem

-> where o_orderdate between ‘1995-01-01‘ and ‘1995-01-31‘

-> and l_orderkey=o_orderkey;

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

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |

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

| 1 | SIMPLE | orders | NULL | range | PRIMARY,i_o_orderdate | i_o_orderdate | 4 | NULL | 40696 | 100.00 | Using where; Using index |

| 1 | SIMPLE | lineitem | NULL | ref | PRIMARY,i_l_orderkey,i_l_orderkey_quantity | PRIMARY | 4 | dbt3.orders.o_orderkey | 3 | 100.00 | NULL |

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

2 rows in set, 1 warning (0.00 sec)

orderkey上有索引,但是没用,用的是pk,orders表示外表,根据过滤条件把数据过滤出来做外表,然后跟lineitem表关联,用的是pk,关联的列是orders.o_orderkey

如果强行走orderkey索引,成本很高,需要回表,通过主键不用回表

案例3

(root@localhost) [dbt3]> DESC select *

-> from

-> lineitem

-> where

-> l_shipdate <= ‘1995-12-32‘

-> union

-> select

-> *

-> from

-> lineitem

-> where

-> l_shipdate >= ‘1997-01-01‘;

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

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |

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

| 1 | PRIMARY | lineitem | NULL | ALL | i_l_shipdate | NULL | NULL | NULL | 5409799 | 33.33 | Using where |

| 2 | UNION | lineitem | NULL | ALL | i_l_shipdate | NULL | NULL | NULL | 5409799 | 50.00 | Using where |

|NULL| UNION RESULT | | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |

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

3 rows in set, 3 warnings (0.10 sec)

union result合并两张表 会using temporary,使用临时表,union会去重,所以又去建了临时表,在上面加了唯一索引,这里就用了两个索引,所以一个sql只能用一条索引是不对的

案例4

(root@localhost) [employees]> DESC SELECT

-> emp_no,

-> dept_no,

-> (SELECT

-> COUNT(1)

-> FROM

-> dept_emp t2

-> WHERE

-> t1.emp_no <= t2.emp_no) AS row_num

-> FROM

-> dept_emp t1;

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

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |

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

| 1 | PRIMARY | t1 | NULL | index | NULL | emp_no | 4 | NULL | 331570 | 100.00 | Using index |

| 2 | DEPENDENT SUBQUERY | t2 | NULL | ALL | PRIMARY,emp_no | NULL | NULL | NULL | 331570 | 33.33 | Range checked for each record (index map: 0x3) |

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

2 rows in set, 2 warnings (0.00 sec)

对于这个sql,先执行了1再执行了2,2是dependent subquery,要依赖子查询,所以先执行了1,所以t1是外表,t2是内表,每次得关联33w * 33%次数,一共关联33w次,一共是33w * 10w次

行号问题,性能非常差

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值