在语句执行之前,MySQL查询优化器会找出执行该语句所有可能的方案,对比找到成本最低的方案,该方案叫 执行计划
成本主要分为两种:
I/O成本:以 页 为单位将数据和索引从 磁盘 加载到 内存 的成本,默认规定读取一个页面的成本是1.0 CPU成本:
从内存读取记录并检测搜索条件、对结果集进行排序等操作的成本,默认是0.2
基于成本的优化步骤:
根据搜索条件找到所有 可能用到的索引
计算使用使用不同索引方案执行查询的代价(优先分析 唯一二级索引)
计算 全表扫描 的代价
对比各种方案代价,找到 执行计划
全表扫描就是把聚集索引的中所有的记录依次和所有条件比较(获取最左侧叶子节点,向右侧遍历比较),把符合条件的放入结果集,由于查询成本=I/O成本+CPU成本
因此全表扫描代价与页面数和表中记录数相关 MySQL设计者将为 每个表 都维护了一系列统计信息
通过show table status
查看:其中的Rows是行数,对于MyISAM是准确值,对InnoDB是估算值;Data_length是表占用的存储空间字节数,对MyISAM是数据文件大小,对InnoDB是聚集索引大小(等同于数据文件)由于每个页面默认16KB,因此可以算出页面的数量。
例:Rows = 9693 , Data_length = 1589248 , 则聚簇索引页面数量得:1589248/16/1024 = 97 ,
IO成本估算:97 * 1.0 +1.1=98.1 公式解析:加载一个页面的成本常数为1.0 ,后面的1.1是个微调值。
CPU成本估算: 9693*02+1.0=1939.6 公式解析:0.2是访问一条记录所需的成本长数,后面的1.0是个微调值。
总成本为: 1939.6+98.1 = 2037.7
对于使用二级索引+回表方式执行的查询,在计算这种查询的成本依赖两方面数据:扫描区间数量和需要回表的记录数。
* 扫描区间数量:无论某个扫描区间的二级索引到底占用了多少页面,查询优化器都认为读取索引一个扫描区间的I/O成本与读取一个页面的I/O成本相同。一个扫描区间的I/O成本即为1 * 1.0 = 1;
* 需要回表的记录数:
a)分别找到区间最左记录与最右记录。
b)如果区间最左记录和区间最右记录相隔不太远(在Mysql5.7.22版本中 只要相隔不大于10个页即可)就可以精准统计出 扫描区间的记录数。
PS:(数据页PageHeader有一个PAGE_N_RECS属性存储了该页面的记录数,所以满足这个不太远的要求,直接遍历这些页面,把页面的PAGE_N_RECS加起来就可以了)
c) 设该扫描区间记录数为 95条记录, 读取95条二级索引记录需要的CPU成本则为 95 * 0.2 + 0.0.1,0.2为读取一条记录的成本常数,0.01是微调值。
* 根据这些记录的主键值到聚簇索引中执行回表操作:
a) 执行回表操作的I/O成本相当于访问一个页面,所以回表操作的I/O成本为 95 * 1.0 = 95
* 回表操作后得到完整的用户记录,然后再检测其他搜索条件是否成立。
a) I/O成本 1.0 + 95 * 1.0 = 96 (扫描区间数量+预估二级索引记录数量)
b)CPU成本:95 * 0.2 + 0.01 + 95 * 0.2 = 38.01 (读取二级索引记录的成本+读取病检测回表操作做后聚簇索引记录的成本)
所以总成本为96+38.01=134.01
通过 索引区间 作为查询条件,MySQL底层会找到区间最左记录和区间最右记录,通过这两个记录向上回溯到 相同祖先节点 通过这些 内节点 的记录数可以获取区间最左记录和区间最右记录的 页面数(准确值),然后从区间最左记录向右读取10个页面,计算 平均 每个页面的记录数。最终通过页面数X平均页面记录数得到索引区间的记录数(估算值)这个过程叫:index dive MySQL也会把IN语句作为 单点区间 来计算成本,如果IN语句中的条件非常多,进行 index dive 效率就会非常低(通过show varibales like %dive%可以查看IN语句参数限制值eq_range_index_dive_limit),因此当IN语句的参数超过限制值不会使用 index dive 而是使用 统计数据 来进行估算(如果使用IN语句却没有用到所引,可能是eq_range_index_dive_limit参数值设置太小,而使用了 统计数据 进行估算) 通过show index from <表名>;可以查看索引的统计数据,其中Cardinality是索引列的基数(去重后记录数的估算值) 通过 show table status like '<表名>'可以查看表空间的统计数据,其中的Rows是表中的记录的估算值 一个值的重复次数≈Rows / Cardinality,得到 重复次数估算值 和 单点区间 的列相乘,就可以得到这些 单点区间 对应的记录条数估算值了