数据库
首页 > 数据库> > MySQL基于成本的优化与成本计算

MySQL基于成本的优化与成本计算

作者:互联网

一条查询语句的执行成本是由下面两个部分组成:

I/O 成本

我们的表经常使用的 MyISAM 、 InnoDB 存储引擎都是将数据和索引都存储到磁盘上的,当我们想查询表中的 记录时,需要先把数据或者索引加载到内存中然后再操作。这个从磁盘到内存这个加载的过程损耗的时间称 之为 I/O 成本

CPU 成本

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

 

对于 InnoDB 存储引擎来说,页是磁盘和内存之间交互的基本单位,MySQL规定读取一个页面花费的 成本默认是 1.0 ,读取以及检测一条记录是否符合搜索条件的成本默认是 0.2 。 1.0 、 0.2 这些数字称之为 成 本常数。
※不管读取记录时需不需要检测是否满足搜索条件,其成本都算是0.2。

单表查询

优化器优化语句的步骤

1. 根据搜索条件,找出所有可能使用的索引

2. 计算全表扫描的代价

  全表扫描的意思就是把聚簇索引中的记录都依次和给定的搜索条件做一下比较,把 符合搜索条件的记录加入到结果集,所以需要将聚簇索引对应的页面加载到内存中,然后再检测记录是否符合搜 索条件。

  由于查询成本= I/O 成本+ CPU 成本,所以计算全表扫描的代价需要两个信息: 聚簇索引占用的页面数 和 该表中的记录数

    可以使用 SHOW TABLE STATUS 语句来查看表的统计信息

    Rows 本选项表示表中的记录条数。对于使用 MyISAM 存储引擎的表来说,该值是准确的,对于使用 InnoDB 存储引 擎的表来说,该值是一个估计值。

    Data_length 本选项表示表占用的存储空间字节数。使用 MyISAM 存储引擎的表来说,该值就是数据文件的大小,对于使 用 InnoDB 存储引擎的表来说,该值就相当于聚簇索引占用的存储空间大小,

    也就是说可以这样计算该值的大小: Data_length = 聚簇索引的页面数量 x 每个页面的大小

    反向来推导出 聚簇索引的页面数量 : 聚簇索引的页面数量 = Data_length ÷ 每个页面的大小 

 

    此时i/o成本=聚簇索引的页面数量 * 1.0(加载一个页面的成本常数)+1.1(一个微调值,固定的)

    cpu成本=rows(是统计数据中表的记录数,估计值) * 0.2(访问一条记录 所需的成本常数)+1.0(一个微调值,固定的)

    总成本=cpu成本+i/o成本

 

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

  MySQL 查询优化器先分 析使用唯一二级索引的成本,再分析使用普通索引的成本,最后还要分析是否可能使用到索引合并。

  范围区间数量

  不论某个范围区间的二级索引到底占用了多少页面,查询优化器粗暴的认为读取索引的一个范围区间的 I/O 成本和读取一个页面是相同的。

  需要回表的记录数

  优化器需要计算二级索引的某个范围区间到底包含多少条记录,对于本例来说就是要计算 idx_key2 在 (10, 1000) 这个范围区间中包含多少二级索引记录,计算过程是这样的:

   步骤1:先根据 key2 > 10 这个条件访问一下 idx_key2 对应的 B+ 树索引,找到满足 key2 > 10 这个条 件的第一条记录,我们把这条记录称之为 区间最左记录 。我们前头说过在 B+ 数树中定位一条记录的过 程是贼快的,是常数级别的,所以这个过程的性能消耗是可以忽略不计的。

  步骤2:然后再根据 key2 < 1000 这个条件继续从 idx_key2 对应的 B+ 树索引中找出第一条满足这 个条件的记录,我们把这条记录称之为 区间最右记录 ,这个过程的性能消耗也可以忽略不计的。

  步骤3:如果 区间最左记录 和 区间最右记录 相隔不太远(在 MySQL 5.7.21 这个版本里,只要相 隔不大于10个页面即可),那就可以精确统计出满足 key2 > 10 AND key2 < 1000 条件的二级索引 记录条数。否则只沿着 区间最左记录 向右读10个页面,计算平均每个页面中包含多少记录,然后 用这个平均值乘以 区间最左记录 和 区间最右记录 之间的页面数量就可以了。

  那怎么估计 区间最左记录 和 区间最右记录 之间有多少个页面呢?

 

  如图,我们假设 区间最左记录 在 页b 中, 区间最右记录 在 页c 中,那么我们想计算 区间最左记 录 和 区间最右记录 之间的页面数量就相当于计算 页b 和 页c 之间有多少页面,而每一条 目录项 记录 都对应一个数据页,所以计算 页b 和 页c 之间有多少页面就相当于计算它们父节点(也就是 页a)中对应的目录项记录之间隔着几条记录。在一个页面中统计两条记录之间有几条记录的成本 就贼小了。 不过还有问题,如果 页b 和 页c 之间的页面实在太多,以至于 页b 和 页c 对应的目录项记录都不 在一个页面中该咋办?继续递归啊,也就是再统计 页b 和 页c 对应的目录项记录所在页之间有多少 个页面。之前我们说过一个 B+ 树有4层高已经很了不得了,所以这个统计过程也不是很耗费性能。

 

  总结:索引扫描的成本计算=i/o成本+cpu成本=(范围区间的数量+预估的二级索引记录条数)+(读取二级索引记录的成本 + 读取并检测回表后聚簇 索引记录的成本)

  

4. 对比各种执行方案的代价,找出成本最低的那一个

 

多表查询的成本(两个表及以上)

  MySQL 中连接查询采用的是嵌套循环连接算法,驱动表会被访问一次,被驱动表可能会被访问多次,对于两表连接查询来说,它的查询成本由下边两个部分构成:

    1.单次查询驱动表的成本

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

  我们把对驱动表进行查询后得到的记录条数称之为驱动表的 扇出 (英文名: fanout )。很显然驱动表的扇出值 越小,对被驱动表的查询次数也就越少,连接查询的总成本也就越低。

  连接查询总成本 = 单次访问驱动表的成本 + 驱动表扇出数 x 单次访问被驱动表的成本

 

多表连接的成本分析

  首先要考虑一下多表连接时可能产生出多少种连接顺序:

   对于两表连接,比如表A和表B连接 只有 AB、BA这两种连接顺序。其实相当于 2 × 1 = 2 种连接顺序。

  对于三表连接,比如表A、表B、表C进行连接 有ABC、ACB、BAC、BCA、CAB、CBA这么6种连接顺序。其实相当于 3 × 2 × 1 = 6 种连接顺序。

  对于四表连接的话,则会有 4 × 3 × 2 × 1 = 24 种连接顺序。 对于 n 表连接的话,则有 n × (n-1) × (n-2) × ··· × 1 种连接顺序,就是n的阶乘种连接顺序, 也就是 n! 。

  有 n 个表进行连接, MySQL 查询优化器要每一种连接顺序的成本都计算一遍。不过 MySQL 设计了很多办法减少计算非常多种连接顺序的成本的方法:

  1. 提前结束某种顺序的成本评估

  MySQL 在计算各种链接顺序的成本之前,会维护一个全局的变量,这个变量表示当前最小的连接查询成本。 如果在分析某个连接顺序的成本时,该成本已经超过当前最小的连接查询成本,那就压根儿不对该连接顺序 继续往下分析了。比方说A、B、C三个表进行连接,已经得到连接顺序 ABC 是当前的最小连接成本,比方 说 10.0 ,在计算连接顺序 BCA 时,发现 B 和 C 的连接成本就已经大于 10.0 时,就不再继续往后分析 BCA 这个连接顺序的成本了。

  2.系统变量 optimizer_search_depth

  为了防止无穷无尽的分析各种连接顺序的成本,设计 MySQL 的大叔们提出了 optimizer_search_depth 系统 变量,如果连接表的个数小于该值,那么就继续穷举分析每一种连接顺序的成本,否则只对与 optimizer_search_depth 值相同数量的表进行穷举分析。很显然,该值越大,成本分析的越精确,越容易 得到好的执行计划,但是消耗的时间也就越长,否则得到不是很好的执行计划,但可以省掉很多分析连接成 本的时间。

  3.根据某些规则压根儿就不考虑某些连接顺序

  即使是有上边两条规则的限制,但是分析多个表不同连接顺序成本花费的时间还是会很长,所以  提出了一些所谓的 启发式规则 (就是根据以往经验指定的一些规则),凡是不满足这些规则的 连接顺序压根儿就不分析,这样可以极大的减少需要分析的连接顺序的数量,但是也可能造成错失最优的执 行计划。他们提供了一个系统变量 optimizer_prune_level 来控制到底是不是用这些启发式规则。

标签:记录,成本计算,查询,索引,MySQL,优化,连接,成本,页面
来源: https://www.cnblogs.com/chengyunblogs/p/15342714.html