首页 > 数据库 >[翻译]——How the MySQL Optimizer Calculates the Cost of a Query (Doc ID 1327497.1)

[翻译]——How the MySQL Optimizer Calculates the Cost of a Query (Doc ID 1327497.1)

时间:2023-11-29 11:31:40浏览次数:40  
标签:Optimizer read Doc mysql 1327497.1 Handler InnoDB MySQL cost

本文是对这篇文章How the MySQL Optimizer Calculates the Cost of a Query (Doc ID 1327497.1)[1]的翻译,翻译如有不当的地方,敬请谅解,请尊重原创和翻译劳动成果,转载的时候请注明出处。谢谢!

适用于:

MySQL 4.0 及后续更高的版本

本文档中的内容适用于任何平台。

目标

了解 MySQL 优化器如何计算SQL语句的查询成本/代价以及如何分析EXPLAIN 语句的输出。

解决方案

MySQL优化器使用成本模型(cost model),其中查询计划的总体成本由各种操作(operation)的成本总体决定。确定成本的主要方法是使用存储提供的统计数据并使用所谓的成本常量(另请参阅下面的“对数据库成本模型进行更改”部分)。例如,这些统计数据是索引基数值(即索引中值的唯一性的度量)和每个表中的总行数。由于统计数据的不精确性,因为统计数据可能已经过时,或者它是使用近似方法来计算获取的统计数据(对于InnoDB数据库尤其如此,见下文),并且值的分布未知,那么优化器只能执行查询时提供查询结果集的行数的估计值。该预估值在某些情况下非常准确,但在某些情况下则不太准确。 实际的实现方式比上面的描述更复杂,确切的细节也可能取决于查询的类型。另请参阅参考手册中的优化器成本模型,了解 MySQL 5.7 及更高版本中优化器成本模型的讨论。 在 MySQL 5.6 及后续更高版本中,当我们在评估优化器如何执行查询语句时,优化器跟踪功能(optimizer trace feature)可用于深入了解优化器的决策过程。更多详细信息,请参阅注释 2241524.1[2]

更改数据库的成本模型

在 MySQL 5.7 以及后续版本中,优化器模型中使用的成本常量(cost constants)由数据库用来进行成本估算。如果需要的话,可以更改此数据库的配置。

警告:更改数据库的成本模型被认为是高级数据库管理。如果您进行更改以验证其行为是否符合预期时,请务必小心,并在部署到生产环境之前进行完全彻底充分的测试。

从 MySQL 5.7.17 开始,数据库成本模型中有两个可以调整的变量。数据存储在mysql.engine_cost表中,默认值为:

mysql> SELECT * FROM mysql.engine_cost;
+-------------+-------------+------------------------+------------+---------------------+---------+
| engine_name | device_type | cost_name              | cost_value | last_update         | comment |
+-------------+-------------+------------------------+------------+---------------------+---------+
| default     |           0 | io_block_read_cost     |       NULL | 2017-01-13 15:21:46 | NULL    |
| default     |           0 | memory_block_read_cost |       NULL | 2017-01-13 15:21:46 | NULL    |
+-------------+-------------+------------------------+------------+---------------------+---------+
2 rows in set (0.00 sec)

io_block_read_cost和memory_block_read_cost参数分别指定从磁盘和内存读取数据的相对成本。将 io_block_read_cost 参数的cost_value值设置为高于 memory_block_read_cost 的值,这会使优化器生成的查询计划更喜欢读取内存中数据,而不是从磁盘读取数据。 还可以通过将engine_name设置为要生效的存储引擎的名称来指定每个存储引擎的成本值。 当所有变更生效后,使用 FLUSH OPTIMIZER_COSTS 命令触发优化器重新读取成本模型数据。 每个会话的优化器成本都会被缓存。只有在 FLUSH OPTIMIZER_COSTS 语句执行之后启动的会话才会受到更改的影响。   例如,通过使用以下步骤将io_block_read_cost默认值设置为 2.0,将 InnoDB 存储引擎的默认值设置为 3.0:

1.将io_block_read_cost的默认值更新为 2.0:

mysql> UPDATE mysql.engine_cost
          SET cost_value = 2.0
        WHERE cost_name = 'io_block_read_cost';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0

2.为InnoDB存储引擎添加新的成本规则,将io_block_read_cost设置为3.0:

mysql> INSERT INTO mysql.engine_cost (engine_name, device_type, cost_name, cost_value, comment)
       VALUES ('InnoDB', 0, 'io_block_read_cost', 3.0, 'Using a slower disk for InnoDB');
Query OK, 1 row affected (0.01 sec)

3.刷新新的成本值:

mysql> FLUSH OPTIMIZER_COSTS;
Query OK, 0 rows affected (0.00 sec)

4.验证新的成本值是否在查询计划中给出了预期结果。

5.根据需要部署到生产。

InnoDB

如果您的表使用InnoDB 存储引擎,您还应该注意索引统计信息是基于有限数量记录的随机样本/采样的估计。如果您运行ANALYZE TABLE tablename(将tablename替换为实际的表名),然后运行SHOW INDEXES FROM tablename,那么您将看到基数将在后续一系列操作之间波动。表中的总行数也是如此。如果多次执行SHOW TABLE STATUS LIKE 'tablename',即使表上没有发生任何更新,那么您将看到每次执行时行总数的估计值也会有所不同(另一方面,对于MyISAM存储引擎表,表中的行数是精确的,因为其缺乏多版本控制和使用表锁,使得维护精确的统计数据变得更容易)。然而,您也可能不走运,最终对要检查的行数的估计相对较差。这也意味着,如果您运行ANALYZE TABLE tablename,那么行列中的值可能会发生变化,在某些情况下甚至查询计划本身也会发生变化。另请参阅MySQL 参考手册中的InnoDB 表限制。

注意:如果innodb_stats_on_metadata设置为ON(MySQL 5.5 及更早版本中的默认值),InnoDB会在元数据语句(例如SHOW TABLE STATUS或SHOW INDEX )执行期间访问INFORMATION_SCHEMA表TABLES或STATISTICS时更新统计信息。

为了更好地估计 InnoDB 表的索引基数,可以将参数innodb_stats_sample_pages设置为更大的值。但需要注意,较大的值将导致索引更新时间更长,并且每次打开表时都会重新计算统计信息,因此可能会对性能产生影响。另请参阅MySQL 优化器团队的Oystein Grovlen 的博客[3],文中讨论了更改innodb_stats_sample_pages的值如何影响索引基数计算。 innodb_stats_sample_pages选项是随 MySQL 5.1.38 中的 InnoDB 插件一起引入的。在MySQL 5.1和MySQL 5.0及更早版本的内置InnoDB中,该值无法更改,并且默认值为8。在 MySQL 5.6.3 中,innodb_stats_sample_pages已替换为innodb_stats_transient_sample_pages选项。对于 MySQL 5.6.2 及更高版本,另请参阅innodb_stats_persistent_sample_pages。   实际检查行数示例 要更好地估计实际检查的行数,请在查询之前和查询完成之后执行SHOW SESSION STATUS LIKE 'handler%'命令。另一种可能性是确保查询被慢查询日志记录,慢查询日志还提供检查的总行数。

使用会话状态变量查找检查的行数的示例是:

mysql> SHOW SESSION STATUS LIKE 'handler%';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Handler_commit             | 0     |
| Handler_delete             | 0     |
| Handler_discover           | 0     |
| Handler_prepare            | 0     |
| Handler_read_first         | 0     |
| Handler_read_key           | 0     |
| Handler_read_last          | 0     |
| Handler_read_next          | 0     |
| Handler_read_prev          | 0     |
| Handler_read_rnd           | 0     |
| Handler_read_rnd_next      | 0     |
| Handler_rollback           | 0     |
| Handler_savepoint          | 0     |
| Handler_savepoint_rollback | 0     |
| Handler_update             | 0     |
| Handler_write              | 0     |
+----------------------------+-------+
16 rows in set (0.00 sec)

mysql> SELECT * FROM (SELECT id FROM t1 WHERE id < 100) t1 INNER JOIN t2 USING (id);
+----+-----+
| id | val |
+----+-----+
|  1 | a   |
|  2 | b   |
|  3 | c   |
...
| 98 | c   |
| 99 | a   |
+----+-----+
76 rows in set (0.00 sec)

mysql> SHOW SESSION STATUS LIKE 'handler%';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Handler_commit             | 1     |
| Handler_delete             | 0     |
| Handler_discover           | 0     |
| Handler_prepare            | 0     |
| Handler_read_first         | 1     |
| Handler_read_key           | 100   |
| Handler_read_last          | 0     |
| Handler_read_next          | 99    |
| Handler_read_prev          | 0     |
| Handler_read_rnd           | 0     |
| Handler_read_rnd_next      | 100   |
| Handler_rollback           | 0     |
| Handler_savepoint          | 0     |
| Handler_savepoint_rollback | 0     |
| Handler_update             | 0     |
| Handler_write              | 99    |
+----------------------------+-------+
16 rows in set (0.00 sec)

这表明SQL查询总共读取了 300 行,分布如下:

  • 1 行读取索引中的第一个条目 (Handler_read_first)
  • 99 行按键顺序读取下一行(Handler_read_next)
  • 100 行从索引查找行(Handler_read_key)
  • 100 行进行表扫描 (Handler_read_rnd_next) 此外,由于派生表 (Handler_write),有 99 行被写入内部临时表。

参考资料

[1]

原文: https://support.oracle.com/epmos/faces/DocumentDisplay?id=1327497.1

[2]

1: https://support.oracle.com/epmos/faces/DocumentDisplay?parent=DOCUMENT&sourceId=1327497.1&id=2241524.1

[3]

Oystein Grovlen: http://oysteing.blogspot.com/2011/04/more-stable-query-execution-time-by.html

标签:Optimizer,read,Doc,mysql,1327497.1,Handler,InnoDB,MySQL,cost
From: https://blog.51cto.com/u_15338523/8613099

相关文章

  • Docker部署heimdall
    一、搭建heimdall绿联DX4600为例,首先我们打开Docker管理器,进入镜像管理,然后在镜像仓库中搜索linuxserver/heimdall​,选择latest​版本并下载。​​下载完成后,我们在本地镜像中找到刚刚下载的镜像,点击创建容器,起一个英文名,勾选创建后启动容器,点击下一步。​​在基础设......
  • kafka docker-compose 单节点部署
    启动脚本sed-i-r"s/[0-9]{1,3}(\.[0-9]{1,3}){3}/$(ifconfig$(iprouteshowdefault|head-1|awk'{print$5}')|grep-oP'(?<=inet\s)\d+(\.\d+){3}')/g"docker-compose.ymldocker-composeup-ddocker-compose.ymlversion......
  • Docker部署ArthasTunnel
    1、下载ArthasTunnel的安装包下载地址:下载  2、部署由于官方只提供了JAR包,如果你想通过Docker方式启动的话,可以自行打包Docker镜像,打包使用的Dockerfile脚本如下:#该镜像需要依赖的基础镜像FROMopenjdk:8-jdk-alpine#将当前目录下的jar包复制到docker容器的/目录下A......
  • docker故障:driver failed programming external connectivity on endpoint
    故障现象Errorresponsefromdaemon:driverfailedprogrammingexternalconnectivityonendpointjenkins(ffdc7c9cda72c575d6b045574d1432b256603a3d986a05da319ab7f3af233755):(iptablesfailed:iptables--wait-tnat-ADOCKER-ptcp-d0/0--dport50000-jDN......
  • Docker + supervisor在同一容器中部署zookeeper和kafka
    使用supervisor进程管理工具,在同一个容器中部署zookeeper和kafka目录Dockerfilejdk1.8.0_181.tar.gzkafka_2.12-1.1.0.tgzconf.ddocker-compose.ymlkafka_conf.dconf.d中为supervisor配置文件kafka_conf.d中为kafka配置文件,解压kafka_2.12-1.1.0.tgz中的配置文件,拷贝......
  • docker 拷贝docker中文件,并获取最新
    1、2、#!/bin/bashdate_str=$(date+_%Y_%m_%d_%H_%M_%S.sql.gz)echo'开始备份数据'$date_str#获取容器idmysqlid=`dockerps-aqf"name=mysql57"`#进入mysql容器dockerexec-i${mysqlid}/bin/bash<<'EOF'mysqldump-hlocalhost-uroot......
  • Dockerfile使用
    什么是Dockerfile?Dockerfile是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。命令注意:Dockerfile的指令每执行一次都会在docker上新建一层。所以过多无意义的层,会造成镜像膨胀过大。可以放在一起的命令尽量放在一起。FROMFROMnginx#FRO......
  • docker离线一键安装脚本
    下载安装文件https://download.docker.com/linux/static/stable/x86_64/https://github.com/docker/compose/releases本例安装文件下载地址https://download.docker.com/linux/static/stable/x86_64/docker-23.0.6.tgzhttps://github.com/docker/compose/releases/download/v......
  • 四、Docker 镜像
    1.什么是镜像UnionFS(联合文件系统):Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(uniteseveraldirectoriesintoasinglevirtualfilesystem)。Union文件系统......
  • docker-compose种不通的服务之间的访问问题,夸容器访问
    背景我们知道对于docker的每个容器都是独立的,想要夸容器访问的话,不能用127.0.0.1加端口号去访问,所以需要docker虚拟网卡的网关分配的地址去访问,可以通过dockerinspect对每个容器的局域网ip进行查看,但是这样比较麻烦,所以有一个新的解决办法,就是通过docker-compose配置文件的方......