点击标题下「蓝色微信名」可快速关注
技术社群的这篇文章《第 56 期:EXPLAIN ANALYZE 怎么用?》给我们讲解了Explain Analyze的应用,由于它会实际执行语句,因此能给出实际的数据,但相应地就会增加执行时间,因此还需要根据场景进行决策。
MySQL执行计划有很多种格式(EXPLAIN
、EXPLAIN FORMAT=JSON
、EXPLAIN FORMAT=TREE
),我们看下 EXPLAIN
语句能做哪些事情?
虽然 EXPLAIN
语句的输出结果是根据 MySQL 的表、索引等统计信息计算而来的,但是这些统计信息一般都是基于特定比例的行来进行计算的,并非全部真实的数据。某些场景下,需要完整执行一遍 SQL 语句后,对比其真实的扫描行数、执行成本等数据来进一步比对进而优化。
此时,EXPLAIN ANALYZE就可以派上用场,因为它会实际执行语句。本文将通过三个例子,介绍 EXPLAIN ANALYZE 语句是如何解析查询结果的。
表 y1
要做全表查询,对比 EXPLAIN
和 EXPLAIN ANALYZE
的效果差异。
仅显示总成本与扫描行数。
mysql:ytt>explain format=tree select * from y1\G
*************************** 1. row ***************************
EXPLAIN: -> Table scan on y1 (cost= rows=1e+6)
row in set (0.00 sec)
除显示总成本和扫描行数外,还显示实际执行的数据。
(mysql:ytt)>explain analyze select * from y1\G
*************************** 1. row ***************************
EXPLAIN: -> Table scan on y1 (cost= rows=1e+6) (actual time=0.0339..145 rows=1e+6 loops=)
row in set (0.20 sec)
其中 (actual time=0.0339..145 rows=1e+6 loops=1)
这条就代表实际执行数据。
对表 t1
、t2
做内连,求满足条件的总记录数,连接 KEY 为 ID。执行计划表示先嵌套循环连接后,再做 COUNT
聚合计算。
mysql:ytt>explain analyze select count(*) from t1 a join t2 b using(id)\G
*************************** 1.row ***************************
EXPLAIN: -> Aggregate: count() (cost=6856.58 rows=) (actual time=28.230..28.230rows= loops=)
-> Nested loop inner join (cost=5839.68 rows=) (actual time=0.057..27.721 rows= loops=)
-> Index scanon b using idx_log_date (cost=1049.90 rows=) (actual time=0.044..2.579 rows= loops=)
-> Single-row index lookup on a using PRIMARY (id=b.id) (cost=0.37 rows=) (actual time=0.002..0.002 rows= loops=)
row in set (0.03 sec)
最终聚合部分:Aggregate: count(0) (cost=6856.58 rows=10169) (actual time=28.230..28.230 rows=1 loops=1)
,预估扫描行数为 10169,实际扫描行数为 1 行,实际执行时间为 28 毫秒。
嵌套循环内联部分:Nested loop inner join (cost=5839.68 rows=10169) (actual time=0.057..27.721 rows=10000 loops=1)
,预估扫描行数为 10169,实际扫描行数为 10000 行,实际总执行时间为 27 毫秒。
b
的扫描基于索引 idx_log_date
。实际执行时间为 2.5 毫秒,扫描行数 1 万,循环 1 次即可;a
的扫描基于主键,过滤条件依赖上层查询得到的连接 KEY,每次扫 1 行,执行时间 0.002 毫秒(可以忽略不计),循环 1 万次。尝试增加过滤条件 a.r1=10
,执行结果多了一条过滤器的内容。
mysql:ytt>explain analyze select count(*) from t1 a join t2 b using(id) where a.r1 = \G
*************************** 1.row ***************************
EXPLAIN: -> Aggregate: count() (cost=5952.35 rows=) (actual time=20.465..20.465rows= loops=)
-> Nested loop inner join (cost=5901.50 rows=) (actual time=0.264..20.447 rows= loops=)
-> Index scanon b using idx_log_date (cost=1049.13 rows=) (actual time=0.070..2.242 rows= loops=)
-> Filter: (a.r1 = ) (cost=0.38 rows=) (actual time=0.002..0.002 rows= loops=)
-> Single-row index lookup on a using PRIMARY (id=b.id) (cost=0.38 rows=) (actual time=0.001..0.002 rows= loops=)
row in set (0.02 sec)
Filter: (a.r1 = 10) (cost=0.38 rows=0) (actual time=0.002..0.002 rows=0 loops=10000)
,实际执行时间为 0.002 毫秒,扫描记录数为 0,循环 1 万次。
也就是说,增加的过滤条件不用扫表,基于主键扫描后再二次过滤即可。
再来看一个派生表过滤的例子。
mysql:ytt>desc analyze select * from (select * from t1 where order by r1 desc limit ) T where r2 < \G
*************************** 1.row ***************************
EXPLAIN: -> Filter: (T.r2 < ) (cost=102.91..115.00 rows=) (actual time=5.524..5.695 rows= loops=)
-> Table scanon T (cost=0.01..15.00 rows=) (actual time=0.002..0.041 rows= loops=)
-> Materialize (cost=102.89..117.87 rows=) (actual time=5.521..5.623 rows= loops=)
-> Limit: row(s) (cost=2.87 rows=) (actual time=0.552..5.197 rows= loops=)
-> Index scanon t1 using idx_r1 (reverse) (cost=2.87 rows=) (actual time=0.551..5.132 rows= loops=)
row in set (0.00 sec)
按照从下到上的顺序解释:
idx_r1
反向扫描。由于有 LIMIT
条件,只需实际扫描 1000 行,执行时间为 5.132 毫秒,循环 1 次即可。最终派生表过滤:Filter: (T.r2 < 1000) (cost=102.91..115.00 rows=333) (actual time=5.524..5.695 rows=1000 loops=1)
。这里预估行数和实际扫描有很大差异,预估行数为 333 行,实际扫描行数为 1000 行,实际执行时间为 5.695 毫秒。
EXPLAIN ANALYZE 由于会实际执行 SQL 语句,当需要分析一条非常耗资源的 SQL 时,需要很长时间才能出结果,不推荐使用。
例如前篇提到过的对 MySQL 非常不优好的多次嵌套语句:select count(*) from t1 where r1 in (select r1 from t1 where r1 in (select r1 from t1))
,就属于这种情况,因此需要结合具体的场景,决策用什么。
如果您认为这篇文章有些帮助,还请不吝点下文章末尾的"点赞"和"在看",或者直接转发朋友圈