前面我们介绍了单表多表的查询成本计算,成本分为 CPU 成本和 IO 成本,其中 CPU 成本默认是 0.2, IO 成本默认是 1.0,这个值是怎么得出来的呢?下面我们介绍一下这些 “成本” 到底是什么。

调节成本常数

在 Mysql 中读取一个页面花费的成本默认是 1.0,检测一条记录是否符合搜索条件的成本默认是 0.2,其实除了这两个成本常数,MySQL 还支持很多,它们被存储到了 MySQL 数据库的两个表中,我么可以使用一下命令来查看:

SHOW TABLES FROM mysql LIKE ‘%cost%’

微信截图_20211221113027.png

我们可以看出这两张表分别是 engine_cost 和 server_cost,因为一条语句的执行其实是分为两层的:server层、存储引擎层。在server层进行连接管理、查询缓存、语法解析、查询优化等操作,在存储引擎层执行具体的数据存取操作。也就是说一条语句在 server 层中执行的成本是和它操作的表使用的存储引擎是没关系的,所以关于这些操作对应的成本常数就存储在了server_cost 表中,而依赖于存储引擎的一些操作对应的成本常数就存储在了engine_cost 表中。

mysql.server_cost表

server_cost 表中在 server 层进行的一些操作对应的成本常数,具体内容如下:

SELECT * FROM mysql.server_cost

微信截图_20211221114524.png

其中 cost_name 表示成本常数名称。cost_value 表示成本常数对应的值,如果为 null 的话会采用默认值。last_update 表示最后更新时间,comment 表示注释。下面我们来逐一介绍一下上面所有成本常数。

  • disk_temptable_create_cost

表示基于磁盘的临时表的成本,如果增大这个值的话会让优化器尽量少的创建基于磁盘的临时表,默认值为 40。

  • disk_temptable_row_cost

表示向基于磁盘的临时表或数据表写入或读取一条记录的成本,默认值为 1.0,如果增大这个值的话会让优化器尽量少的创建基于磁盘的临时表。

  • key_compare_cost

表示两条记录做比较操作的成本,多用在排序操作上, 默认值为 0.1,如果增大这个值的话会提升 filesort 的成本,让优化器可能更倾向于使用索引完成排序而不是 filesort。

  • memory_temptable_create_cost

表示创建基于内存的临时表的成本,默认值为 2.0,如果增大这个值的话会让优化器尽量少的创建基于内存的临时表。

  • memory_temptable_row_cost

表示向基于内存的临时表写入或读取一条记录的成本,默认值为 0.2,如果增大这个值的话会让优化器尽量少的创建基于内存的临时表。

  • row_evaluate_cost

这个就是我们之前一直使用的检测一条记录是否符合搜索条件的 CPU 成本,默认值为 0.2,增大这个值可能让优化器更倾向于使用索引而不是直接全表扫描。

MySQL在执行诸如 DISTINCT 查询、分组查询、Union 查询以及某些特殊条件下的排序查询都可能在内部先创建一个临时表,使用这个临时表来辅助完成查询(比如对 DISTINCT
查询可以建一个带有 UNIQUE 索引的临时表,直接把需要去重的记录插入到这个临时表
中,插入完成之后的记录就是结果集了)。在数据量大的情况下可能创建基于磁盘的临
时表,也就是为该临时表使用 MyISAM、InnoDB 等存储引擎,在数据量不大时可能创建基于内存的临时表,也就是使用 Memory 存储引擎。大家可以看到,创建临时表和对这个临时表进行写入和读取的操作代价还是很高的。

这些成本常数在server_cost中的初始值都是NULL,意味着优化器会使用它们的默认值来 计算某个操作的成本,如果我们想修改某个成本常数的值的话,需要做两个步骤:

  • 对我们感兴趣的成本常数做 update 更新操作
  • 然后使用 “FLUSH OPTIMIZER_COSTS” 语句重新加载

当然,在你修改完某个成本常数后想把它们再改回默认值的话,可以直接把 cost_value
的值设置为NULL,再使用 FLUSH OPTIMIZER_COSTS 语句让系统重新加载。

mysql.engine_cost表

engine_cost 表中存储引擎层进行的一些操作对应的成本常数,具体内容如下:

SELECT * FROM mysql.engine_cost

image.png

与 server_cost 相比,engine_cost 多了两个列:engine_name 表示成本常数适用的存储引擎名称,如果该值为 default,意味着对应的成本常数适用于所有的存储引擎。device_type 表示存储引擎使用的设备类型,这主要是为了区分常规的机械硬盘和固态硬盘,不过在 MySQL 5.7.X 这个版本中并没有对机械硬盘的成本和固态硬盘的成本作区分,所以该值默认是0。

我们从 engine_cost 表中的内容可以看出来,目前支持的存储引擎成本常数只有两个:

  • io_block_read_cost

表示从磁盘上读取一个块对应的成本,默认值为 1.0 。请注意我使用的是块,而不是页这个词。对于 InnoDB 存储引擎来说,一个页就是一个块,不过对于 MyISAM存 储引擎来说,默认是以 4096 字节作为一个块的。增大这个值会加重I/O成本,可能让优化器更倾向于选择使用索引执行查询而不是执行全表扫描。

  • memory_block_read_cost

与上一个参数类似,只不过衡量的是从内存中读取一个块对应的成本,默认值为 1.0。

有没有发现,怎么从内存中和从磁盘上读取一个块的默认成本是一样的?这主要是因为在MySQL 目前的实现中,并不能准确预测某个查询需要访问的块中有哪些块已经加载到内存中,有哪些块还停留在磁盘上,所以 MySQL 简单的认为不管这个块有没有加载到内存中,使用的成本都是1.0。

与更新 server_cost 表中的记录一样,我们也可以通过更新engine_cost 表中的记录来更改关于存储引擎的成本常数,做法一样。