第12章 谁便宜就选谁——基于成本的优化

成本 #

一条查询语句在MySQL中的执行成本,由两方面组成:

  • I/O成本:(此处指将数据存储在磁盘上的存储引擎)当查询表中的记录时,需要先把数据或者索引加载到内存中,然后再进行操作。这个从磁盘到内存到加载过程损耗的时间称为I/O成本。

  • CPU成本:读取记录以及检测记录是否满足对应的搜索条件、对结果集进行排序等这些操作损耗的时间称为CPU成本。

成本常数 #

MySQL规定:

  • 读取一个页面花费的成本默认是1.0;
  • 读取以及检测一条记录是否符合搜索条件的成本默认是0.2。 1.0、0.2这些数字称为成本常数。
在读取记录时,即使不需要检测记录是否符合搜索条件,其成本也算0.2。

优化器确定执行计划的步骤 #

优化器会查找成本最低的执行计划,其步骤如下:

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

计算全表扫描的代价 #

计算全表扫描的代价时需要两个信息:

  • 聚簇索引占用的页面数。
  • 该表中的记录数。 这些信息都是从MySQL维护的一系列统计信息中获取的。

可以通过show table status命令查看表的统计信息。

计算使用不同索引执行查询的代价 #

对于使用二级索引+回表方式执行的查询,MySQL在计算这种查询的成本时,依赖于两方面的数据:扫描区间数量和需要回表的记录数。

  • 扫描区间的数量 查询优化器认为读取索引的一个扫描区间的I/O成本与读取一个页面的I/O成本相同,即1.0。
  • 需要回表的记录数 如果区间最左记录和最右记录相隔不远,可以精确统计。 否则,沿着区间最左记录向右读取10个页面,计算每个页面平均包含多少记录,然后用这个平均值乘以区间的页面数量。区间的页面数量可以根据目录项来确定。

回表的成本 #

MySQL在评估回表的I/O成本时,认为每次回表操作都相当于访问一个页面。

索引成本统计 #

index dive #

MySQL把直接访问索引对应的B+树来计算某个扫描区间内对应的索引记录条数的方式称为index dive。

show index #

如果in 语句产生的单点扫描区间的数量小于200个,将使用index dive来计算各个区间的记录条数。如果大于等于200个,要使用索引统计数据。

可以使用show index from 表名命令查看索引统计数据。 如下所示: show index 其中:

  • Cardinality:该列中不重复值的数量。

连接查询的成本 #

两表连接 #

两表连接的成本由两部分组成:

  • 单次查询驱动表的成本。
  • 多次查询被驱动表的成本(具体查询多少次取决于针对驱动表查询有的结果集中有多少条记录)。

扇出 #

我们把查询驱动表后得到的记录条数称为驱动表的扇出。

查询成本计算公式: 连接查询总成本 = 单次访问驱动表的成本+ 驱动表扇出值*单次访问被驱动表的成本。

多表连接 #

多表连接,可能会生成多种连接顺序,为了防止无穷无尽的分析各种连接顺序的成本,MySQL提供了optimizer_search_depth参数来限制优化器分析连接顺序的成本。如果连接表的个数小于该值,就继续穷举每一种连接顺序的成本,否则只对数量与optimizer_search_depth相等的连接表进行穷举。

调节成本常数 #

server层操作对应的成本常数存储在server_host表中,依赖于存储引擎的操作对应的成本常数存储在engine_cost表中。这两张表都在mysql库中。