作者:胡呈清爱可生 DBA 团队成员,善于故障剖析、性能优化,集体博客:https://www.jianshu.com/u/a95...,欢送探讨。
本文起源:原创投稿
*爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。
explain format=json 能够打印具体的执行计划成本,上面两个示例将通知你如何查看老本输入,以及如何计算成本。
表构造如下:
mysql> show create table sbtest1\G*************************** 1. row *************************** Table: sbtest1Create Table: CREATE TABLE `sbtest1` ( `id` int(11) NOT NULL AUTO_INCREMENT, `k` int(11) NOT NULL DEFAULT '0', `c` char(120) COLLATE utf8mb4_bin NOT NULL DEFAULT '', `pad` varchar(90) COLLATE utf8mb4_bin DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4316190 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin##留神sbtest3无主键mysql> show create table sbtest3\G*************************** 1. row *************************** Table: sbtest3Create Table: CREATE TABLE `sbtest3` ( `id` int(11) NOT NULL, `k` int(11) NOT NULL DEFAULT '0', `c` char(120) COLLATE utf8mb4_bin NOT NULL DEFAULT '', `pad` varchar(66) COLLATE utf8mb4_bin DEFAULT NULL, KEY `k_3` (`k`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
示例 1
mysql> explain format=json select * from sbtest3 where id<100 and k<200\G*************************** 1. row ***************************EXPLAIN: { "query_block": { "select_id": 1, "cost_info": { "query_cost": "26.21" ##查问总成本 }, "table": { "table_name": "sbtest3", ##表名 "access_type": "range", ##拜访数据的形式是range,即索引范畴查找 "possible_keys": [ "k_3" ], "key": "k_3", ##应用索引 "used_key_parts": [ "k" ], "key_length": "4", "rows_examined_per_scan": 18, ##扫描 k_3 索引的行数:18(满足特定条件时应用index dive可失去真履行数) "rows_produced_per_join": 5, ##在扫描索引后估算满足id<100条件的行数:5 "filtered": "33.33", ##在扫描索引后估算满足其余条件id<100的数据行占比 "index_condition": "(`sbtest`.`sbtest3`.`k` < 200)", ##索引条件 "cost_info": { "read_cost": "25.01", ##这里蕴含了所有的IO老本+局部CPU老本 "eval_cost": "1.20", ##计算扇出的CPU老本 "prefix_cost": "26.21", ##read_cost+eval_cost "data_read_per_join": "4K" }, "used_columns": [ "id", "k", "c", "pad" ], "attached_condition": "(`sbtest`.`sbtest3`.`id` < 100)" } }}
eval_cost
这个很简略,就是计算扇出的 CPU 老本。利用条件 k<200 时,须要扫描索引 18行,这里 18 是准确值(index dive),而后优化器用了一种叫启发式规定(heuristic)的算法估算出其中满足条件 id<100 的比例为 33.33%,进行 18*33.33%
次计算的 CPU 老本等于 18*33.33%*0.2=1.2
,这里 0.2 是老本常数(即 row_evaluate_cost )。
留神:rows_examined_per_scan*filtered 才是扇出数,不能简略的用 rows_produced_per_join 来示意。
read_cost
这里蕴含了所有的 IO 老本 +(CPU 老本 - eval_cost)。咱们先看下这个SQL的总成本应该怎么算:
拜访二级索引 k_3 的老本:
- IO 老本 =
1*1.0
查问优化器粗犷的认为读取索引的一个范畴区间的 I/O 老本和读取一个页面是雷同的,这个 SQL 中 k 字段的筛选范畴只有 1 个:k < 200,而读取一个页面的 IO 老本为 1.0(即 io_block_read_cost);
- CPU 老本 =
18*0.2
从 k 索引中取出 18 行数据后,理论还要再计算一遍,每行计算的老本为 0.2。
而后因为 select * 以及 where id<100 须要的数据都不在索引 k_3 中,所以还须要回表,回表老本:
- IO 老本 =
18*1.0
从索引中取出满足 k<200 的数据一共是 18 行,所以 = 18*1.0
;
- CPU 老本 =
18*0.2
从这 18 行残缺的数据中计算满足 id<100 的数据,所以也须要计算 18 次。
总成本 = 1*1.0+18*0.2+18*1+18*02=26.2
。因为 eval_cost 算的是扇出的 CPU 老本:18*33.33%*0.2
,所以 read_cost = 回表的 CPU 老本 - eval_cost
,也能够这么算 rows_examined_per_scan*(1-filtered)*0.2
。
示例 2
mysql> explain format=json select t1.id from sbtest1 t1 join sbtest3 t3 \on t1.id=t3.id and t3.k<200 and t3.id<100\G*************************** 1. row ***************************EXPLAIN: { "query_block": { "select_id": 1, "cost_info": { "query_cost": "33.41" ##查问总成本 }, "nested_loop": [ ##join算法:NLJ { "table": { "table_name": "t3", ##t3是驱动表 "access_type": "range", ##拜访数据的形式是range,即索引范畴查找 "possible_keys": [ "k_3" ], "key": "k_3", ##应用的索引:k_3 "used_key_parts": [ ##索引字段:k "k" ], "key_length": "4", "rows_examined_per_scan": 18, ##k_3索引扫描行数:18 "rows_produced_per_join": 5, ##(估算值)扫描索引18行后,满足条件id<200的行数 "filtered": "33.33", ##(估算值)扫描索引18行后,满足条件id<200的数据占扫描行数的比例,即驱动表扇出 "index_condition": "(`sbtest`.`t3`.`k` < 200)", "cost_info": { "read_cost": "25.01", ##这里蕴含了所有的IO老本+局部CPU老本 "eval_cost": "1.20", ##计算扇出的CPU老本 "prefix_cost": "26.21", ##驱动表的总成本:read_cost+eval_cost "data_read_per_join": "4K" }, "used_columns": [ "id", "k" ], "attached_condition": "(`sbtest`.`t3`.`id` < 100)" } }, { "table": { "table_name": "t1", ##t1为被驱动表 "access_type": "eq_ref", ##关联查问时拜访驱动表形式是通过主键或惟一索引的等值查问 "possible_keys": [ "PRIMARY" ], "key": "PRIMARY", ##应用索引为主键 "used_key_parts": [ ##索引字段为id "id" ], "key_length": "4", "ref": [ "sbtest.t3.id" ], "rows_examined_per_scan": 1, ##关联查问时,每次扫描被驱动表1行数据(应用主键) "rows_produced_per_join": 5, ##被驱动表须要查问的次数,不是精确的驱动表扇出数 "filtered": "100.00", ##满足关联条件数据占扫描行数的比例,被驱动表上看这个没啥意义 "using_index": true, "cost_info": { ##驱动表扇出数:rows_examined_per_scan*filtered,即18*33.33%=6行 "read_cost": "6.00", ##单次查问被驱动表的IO老本*驱动表扇出数。6*1.0=6,1.0为老本常数 "eval_cost": "1.20", ##单次查问被驱动表的CPU老本*驱动表扇出数。6*0.2=1.2,0.2位老本常数 "prefix_cost": "33.41", ##查问总成本=驱动表的总成本+被驱动表的(read_cost+eval_cost) "data_read_per_join": "5K" }, "used_columns": [ "id" ] } } ] }}
join 查问的总成本计算公式简化:连贯查问总成本 = 拜访驱动表的老本 + 驱动表扇出数 * 单次访问被驱动表的老本
。explain 执行打算详解 1 中有解释 filtered 在关联查问中的重要性。
在下面示例中:拜访驱动表的老本 = 26.21,驱动表扇出数 = 18*33.33% = 6,单次访问驱动表的老本 = 1.0+0.2
总成本=26.21+6(1.0+0.2)=33.41
留神:驱动表和被驱动表的 read_cost、eval_cost 代表不一样的老本。