1、什么是成本

IO成本:从磁盘到内存的加载过程损耗的时间称为IO成本
CPU成本:读取记录以及检测记录是否满足对应的搜索条件、对结果集进行排序等操作损耗的时间称为CPU成本

成本常数规定:
读取一个页面的成本默认为1
读取以及检测一条记录是否符合搜索的成本默认为0.2

2、单表查询的成本

  1. 根据搜索条件,找出可能用到的索引
  2. 计算全表扫描的代价
  3. 计算使用不同索引执行查询的代价
  4. 对比各种执行方案的代价,找出成本最低的方案

计算全表扫描的代价
查询成本= IO成本+ CPU成本,所以计算全表扫描的代价时需要知道两个信息,

  • 聚集索引占用的页面数
  • 该表中的记录数

通过show table status查看表的统计信息,例如

  1. show table status like 'employees'

employees统计信息
image.png

Rows表示表中的记录数,对MyISAM存储引擎的表来说是准确的,对于使用InnoDB存储引擎的表来说,该值是估计值。
Data_lengh表示表占用存储空间字节数,对MyISAM存储引擎的表来说,该值是数据文件的大小,对于使用InnoDB存储引擎的表来说,该值相当于聚集索引占用的存储空间大小。
Data_lengh = 聚集索引页面数量 * 每个页面页面大小(默认一页16KB)
所以可以反推出聚集索引页面数量 = Data_lengh /16/1024

IO成本:Data_lengh /16/1024 1.0 + 1.1(1.0是读取一个页面的成本,1.1是一个微调值)
CPU成本: Rows
0.2 +1.0 (0.2是读取以及检测一条记录是否符合搜索的成本,1.0是一个微调值)


计算使用不同索引执行查询的代价
不仅要考虑使用不同索引查询还要考虑索引合并的情况
计算索引查询成本时需要知道两个信息,以key1索引为例:

  1. - 扫描区间数量(查询优化器粗暴的认为读取索引的一个扫描区间的IO成本和读取一个页面的IO成本是一样的)
  2. - 需要回表的记录数

如果区间最左记录和最右记录相隔不大于10个页面(100100的第一条记录称为区间最左记录,key1<1000的最后一条记录被称为区间最右记录),则可以精确统计出二级索引记录的条数
如果区间最左记录和最右记录相隔大于10个页面,则从左向右读10个页面,计算每个页面平均包含多少记录(页面的Page Header属性中有个
PAGE_N_RECS属性,代表该页面有多少记录,所以遍历页面将页面中的PAGE_N_RECS属性值加起来就是总记录数,然后除以页面数即为页面平均记录数)。然后计算最左记录所在页面a和最右记录所在页面b之间的页面数,计算页面a,b间的页面数就相当于计算目录项记录之间隔的记录数。用页面数乘以页面平均记录数即可获取需要回表的记录数。

计算成本:
IO成本: 扫描区间数量 1.0 + 回表记录数 1.0(一次回表操作的成本相当于访问一个页面,即一次io成本)+ 1.0(微调值)
CPU成本:读取二级索引的成本(回表数量 0.2) + 回表后检查其它搜索条件是否成立的成本(回表数量 0.2) + 0.01(微调值)

3、连接查询的成本

驱动表的扇出:查询驱动表得到的记录数

连接查询成本 = 单次访问驱动表的成本 + 驱动表扇出值 * 单次访问驱动表的成本

所以优化的重点是

  • 尽量减少驱动表的扇出
  • 访问被驱动表的成本要尽量低

连接查询需要根据连接类型来计算成本:

对于左(外)连接和右(外)连接查询来说,一般情况下(左(外)连接和右(外)连接在某些特殊情况下可以被优化为内连接,即上文提到的外连接消除)驱动表是固定的,所以只需要分别为驱动表和被驱动表选择成本最低的访问方法即可。

对于内连接来说,驱动表和被驱动表可以互换,当选择不同驱动表时,最终的查询成本可能不同,所以需要考虑最优的表连接顺序然后分别为驱动表和被驱动表选择成本最低的访问方法。

4、调节成本常数

mysql支持很多成本常数,因为一条语句执行时,分别在server层和存储引擎层执行,server层进行连接管理,查询缓存,语法解析,查询优化等操作,在存储引擎执行具体的数据存取操作。
server层进行操作对应的成本常数存储在server_cost表中,而存储引擎层对应的成本常数存储在engine_cost表中。

4.1、mysql.server_cost表

select * from mysql.server_cost;

image.png

cost_name:成本常数名称
cost_value:成本常数对应的值。如果该值为null,则说明对应的成本常数采用默认值
last_update:最后更新记录时间
comment:注释
default_value:默认值

成本常数名称 默认值 描述
disk_temptable_create_cost 20 创建基于磁盘的临时表的成本。如果增大这个值,则会让查询优化器尽可能少地创建基于磁盘的临时表
disk_temptable_row_cost 0.5 向基于磁盘的临时表写入或读取一条 记录的成本。如果增大这个值,则会让查询优化器尽可能少地创建基于磁盘的临时表
key_compare_cost 0.05 两条记录进行比较操作的成本,多用在排序操作中。如果增大这个值,则会提升filesort的成本,从而让查询优化器更倾向于使用索引(而不是filesort)完成排序
memory_temptable_create_cost 1 创建基于内存的临时表的成本。如果增大这个值,则会让查询优化器尽可能少地创建基于内存的临时表
memory_temptable_row_cost 0.1 向基于内存的临时表写入或读取一条记录的成本。 如果增大这个值,则会让查询优化器尽可能少地创建基于内存的临时表
row_evaluate_cost 0.1 读取并检测一条记录是否符合搜索条件的成本。如果增大这个值,可能会让查询优化器更倾向于使用索引而不是全表扫描

4.2、mysql.engine_cost表

select * from mysql.engine_cost;

image.png

engine_name: 成本常数适合的存储引擎名称。default表示适合所有的存储引擎。
device_type:存储引擎使用的设备类型,主要是为了区分固态硬盘和机械硬盘。

成本常数名称 默认值 描述
io_block_read_cost 1 从磁盘上读取一个块对应的成本。请注意这里使用的是“块”,而不是“页”。对于InnoDB存储引擎来说,一个页就是一个块,不过对于MyISAM存储引擎来说,默认以4,096字节作为一个块
memory_block_read_cost 0.25 与上一个成本常数类似,只不过衡量的是从内存中读取一个块对应的成本。