查询优化器是 MySQL 的核心子系统之一,成本计算又是查询优化器的核心逻辑。
全表扫描成本作为参照物,用于和表的其它访问方式的成本做对比。任何一种访问方式,只要成本超过了全表扫描成本,就不会被使用。
基于全表扫描成本的重要地位,要讲清楚 MySQL 的成本计算逻辑,从全表扫描成本计算开始是个不错的选择。
全表扫描成本计算
定义
我们先来看一下Mysql源码里成本计算的定义:
class Cost_estimate {
private:
// cost of I/O operations
double io_cost;
// cost of CPU operations
double cpu_cost;
// cost of remote operations
double import_cost;
// memory used (bytes)
double mem_cost;
......
}
复制代码
从上面代码可以看到,MySQL 成本计算模型定义了四种成本:
IO 成本:从磁盘或内存读取数据页的成本。
CPU 成本:访问记录需要消耗的 CPU 成本。
导入成本:这一项一直没被使用,先忽略。
内存成本:这一项指的是占用内存字节数,计算 MRR(Multi Range Read)方式读取数据的成本时才会用到,也先忽略。
const double ratio = static_cast<double>(n_in_mem) / n_leaf;
// 取值只能在 0.0 ~ 1.0 之间
return (std::max(std::min(ratio, 1.0), 0.0));
}
复制代码
InnoDB 在内存中维护了一个哈希表(buf_stat_per_index->m_store),key 是表名,value 是表的主键索引已经加载到 Buffer Pool 中的叶子结点数量。
每次从磁盘加载某个表的主键索引的一个叶子结点数据页到 Buffer Pool 中,该表在buf_stat_per_index->m_store中对应的 value 值就加一。
从 Buffer Pool 的 LRU 链表淘汰某个表的主键索引叶子结点时,该表在buf_stat_per_index->m_store中对应的 value 值就减一。
还有其它场景,buf_stat_per_index->m_store 中的 value 值也会发生变化,不展开了。
成本常数
以如下sql为例
列resource_type的搜索条件是 BETWEEN 1 AND 2,形成的扫描区间就是[1,2]。**优化器规定,读取二级索引的一个扫描区间的IO成本,和读取一个页面的IO成本相同,无论它占用多少页面。(这个是规定,大家记住就好了)因此二级索引页的IO成本就是1.0。
接下来就是估算二级索引过滤后的记录数量了,也就是满足resource_type BETWEEN 1 AND 2的记录数量。MySQL是这样预估的: