MySQL底层内核COST成本计算
Trace 执行计划分析
通过explain 执行计划可以看到优化器决定 走索引还是走全表扫描,但是却无法知道 优化器是如何做 决策的。
MySQL 5.6后,MySQL新增 optimizer trace功能,可通过该功能查看优化器生成执行计划的整个过程,并看到优化器是如何通过成本计算进行决策。
trace命令:
1 | SET optimizer_trace = "enabled=on"; |
1 | { |
通过上面的例子,我们可以得出主键索引无法使用,u_idx_day_status 联合索引也无法使用,只可以使用 全表扫描、idx_order_no、idx_expire_time。
全表扫描cost:2223.3,idx_order_no cost:72.61,idx_expire_time cost 47.81 ,所以执行计划也应当使用 idx_expire_time索引进行查询。
MySQL查询成本
I/O成本(1.0)
- 查询记录时,需先把数据加载到内存中再操作,加载过程损耗时间称为 I/O成本
- MySQL读取一个页(Page,16KB)数据的成本默认1.0
CPU成本(0.2)
- 读取及检测记录是否满足查询条件、对结果集进行排序等操作损耗的时间 称为CPU成本
- MySQL读取一行数据 进行匹配比较的成本 默认0.2
基于成本的优化步骤
- 根据搜索条件,分析出 可能使用的索引
- 计算全表扫描的成本
- 全表扫描:在聚簇索引树数据全部遍历一次后,根据搜索条件进行比对
- 计算使用 不同索引执行查询的成本
- 对比各种执行方案的成本,找出成本最低的
全表扫描成本计算
注意:实测在5.7是0误差,成本计算过程均可正常计算,但是8.0会有一定误差,尚不明确为什么8.0存在误差,可能是 优化器的算法变了。
计算全表扫描前需要 先知道2个参数:
- 聚簇索引占用的页面(Page)
- 该表总记录数
SHOW TABLE STATUS LIKE 'order_exp'
- Rows
- 需要注意的是Rows 是预估值,并不精确
- Data_length
- 如果存储引擎是MyISAM,则表示 数据文件大小,如果存储引擎是InnoDb,则表示 聚簇索引占用空间大小
- Data_length = 聚簇索引页面数量 * 每个页面大小(默认16KB)
- 聚簇索引页面反推:Data_length(字节) ÷ 1024 ÷ 16 ;1589248÷1024÷16 = 97 ,所以当前该表 聚簇索引是 97页
IO成本
计算公式:聚簇索引页面 * 每页成本 + 微调值 = IO成本
1 | 97 * 1.0 + 1.1 = 98.1 |
CPU成本计算
计算公式:表的总记录数 * CPU成本 + 微调值 = CPU成本
1 | 10621 * 0.2 + 1.0 = 2125.2 |
总成本
计算公式:IO成本+CPU成本 = 总成本
98.1+2125.2 = 2203.5,综上所述 对于 order_exp全表扫描的成本是 2203.5,也和MySQL优化器计算的成本一致。
不同索引成本计算
idx_expire_time
idx_expire_time对应的搜索条件是:expire_time > '2021-03-22 18:28:28' AND expire_time <= '2021-03-22 18:35:09'
idx_expire_time范围区间:startTime 2021-03-22 18:28:28 - overTime 2021-03-22 18:35:09,只有一个范围区间。
使用 idx_expire_time 查询会用到 二级索引和回表(因为select *)的方式,MySQL计算这种查询的成本需要依赖2个数据:
- 范围区间IO成本
- idx_expire_time只有一个区间范围,成本计算:1(区间数) * 1.0 = 1.0
- 需要回表查询的记录数
- 根据startTime 作为条件去 idx_expire_time的B+Tree搜索,找到满足条件的第一条记录,称为 区间最左记录
- 根据overTime 作为条件去 idx_expire_time的B+Tree搜索,找到满足条件的最后一条记录,称为 区间最右记录
- 如果最左记录和最右记录 之间区间不大于10页(Page,索引树的页),那就可以精确统计出 区间满足条件的 二级索引记录数量
- 如果大于10页,则从 区间最左记录往右读取10页,计算出 平均每页的记录数,然后 乘以 最左记录到最右记录中间的页数
idx_expire_time区间成本
区间成本计算:1 * 1.0 = 1.0
idx_expire_time回表查询记录数
explain SELECT * FROM order_exp WHERE expire_time > '2021-03-22 18:28:28' AND expire_time <= '2021-03-22 18:35:09'
- 把之前的SQL单独拎出来 查看执行计划,可以发现 区间范围内一共有39行。
- 回表成本计算公式:39 * 0.2 =7.8
I/0成本
- 1 * 1.0 + 39 * 1.0 = 40 .0
- 计算公式:(范围区间数量 * 成本1.0) + (范围区间预估的二级索引记录总数 * 1.0成本)
- 1.0是范围区间的数量 + 39是预估的二级索引记录条数,1.0是加载每条数据的IO成本
CPU成本
- 39 * 0.2 + 0.01 + 39 * 0.2 = 15.61
- 计算公式: 范围区间预估的二级索引记录总数 * CPU成本 + 0.01(微调数) + 回表的成本(回表行数*0.2 每行成本)
总成本
总成本计算公式:I/O成本 + CPU成本
总成本为:40 .0 + 15.61 = 55.61,我们发现和trace的analyzing_range_alternatives有些出入,这是因为 在trace 中,在预估成本时,CPU成本不计算回表成本。
只有在最终决定使用某索引的时候,才会 把回表成本加进去。
idx_order_no
idx_order_no区间成本
区间成本计算:1 * 1.0 = 1.0,idx_order_no有3个区间,所以成本是3.0
idx_order_no回表查询记录数
explain SELECT * FROM order_exp WHERE order_no IN ( 'DD00_6S', 'DD00_9S', 'DD00_10S' );
- 区间范围内一共有58行。
- 回表成本计算公式:58 * 0.2 =7.8
I/0成本
- 3 * 1.0 + 58 * 1.0 = 61
- 计算公式:(范围区间数量 * 成本1.0) + (范围区间预估的二级索引记录总数 * 1.0成本)
- 1.0是范围区间的数量 + 39是预估的二级索引记录条数,1.0是加载每条数据的IO成本
CPU成本
- 58 * 0.2 + 0.01 + 58 * 0.2 = 23.21
- 计算公式: 范围区间预估的二级索引记录总数 * CPU成本 + 0.01(微调数) + 回表的成本(回表行数*0.2 每行成本)
总成本
总成本计算公式:I/O成本 + CPU成本
总成本:61+23.21= 84.21 ,因为MySQL在预估成本时,CPU成本不计算回表成本。
此时我们在扣掉 回表成本: 84.21 - 58*0.2 = 72.61 ,《idx_order_no开销成本》一致。