首页 > 数据库 >[翻译]——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-06 22:01:26浏览次数:38  
标签:Optimizer read Doc mysql 1327497.1 Handler innodb MySQL cost

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

适用于:

MySQL 4.0 及后续更高的版本

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

目标

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

解决方案

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

更改数据库的成本模型

在 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 的博客,文中讨论了更改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 行被写入内部临时表。

参考资料:

NOTE:2241524.1 - How To Use the Optimizer Trace Feature in MySQL 5.6 and Later to Investigate Why a Given Query Plan Was Chosen?

https://dev.mysql.com/doc/refman/en/cost-model.html

NOTE:1464378.1 - Optimizer Enhancements in MySQL 5.6: Multi-Range Read (MRR), Block Nested-Loop (BNL), Batched Key Access (BKA)

NOTE:1328010.1 - Differing Query Execution Times

NOTE:1327825.1 - How Index Distribution Influences the MySQL Optimizer Query Plan

NOTE:1328002.1 - Controlling InnoDB Estimated Optimizer Statistics in MySQL 5.1 and Later

https://dev.mysql.com/doc/refman/5.5/en/innodb-storage-engine.html

https://dev.mysql.com/doc/refman/5.5/en/myisam-storage-engine.html

NOTE:1364899.1 - The MySQL Optimizer Does Not Use the Full Width of the Index

https://dev.mysql.com/doc/refman/5.5/en/innodb-restrictions.html

http://en.wikipedia.org/wiki/cardinalitydelete reference

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

https://dev.mysql.com/doc/refman/5.5/en/glossary.html#glos_cardinality

https://dev.mysql.com/doc/refman/5.5/en/innodb-parameters.html#sysvar_innodb_stats_on_metadata

NOTE:1451889.1 - What is the Difference Between OPTIMIZE TABLE and ANALYZE TABLE in MySQL Server?

https://dev.mysql.com/doc/refman/5.5/en/innodb-parameters.html#sysvar_innodb_stats_sample_pages

https://dev.mysql.com/doc/refman/5.5/en/show-status.html

https://dev.mysql.com/doc/refman/5.5/en/analyze-table.html

https://dev.mysql.com/doc/refman/5.5/en/show-index.html

https://dev.mysql.com/doc/refman/5.5/en/show-table-status.html

NOTE:1023539.1 - What To Do If MySQL Does Not Execute the Wished Query Plan

NOTE:2317760.1 - Example Discussion of How the Optimizer Works

标签:Optimizer,read,Doc,mysql,1327497.1,Handler,innodb,MySQL,cost
From: https://www.cnblogs.com/kerrycode/p/17813862.html

相关文章

  • Docker下安装nacos
    1、拉取镜像dockerpullnacos/nacos-server2、挂载目录,用于映射到容器,目录按自己的情况创建dockervolumecreatenacos_testmkdir-p/var/lib/docker/volumes/nacos_test/logs/                     #新建logs目录mkdir-p/var/lib/docker/v......
  • Docker搭建jenkins环境
    按照习俗,先介绍下jenkins,Jenkins,是一个开源的、提供友好操作界面的持续集成(CI)工具,主要用于持续、自动的构建的一些定时执行的任务。Jenkins用Java语言编写,可在Tomcat等流行的容器中运行,也可独立运行。然后是为什么要使用jenkins:第一Jenkin是免费开源的,且拥有庞大的社区,也就意味着......
  • 部署Docker可视化管理工具docker.ui
    "DockerUI"这个术语在正式的Docker官方文档中并没有明确定义。然而,我们可以将其解释为用户界面(UI),用于管理和监控Docker容器和镜像。通常情况下,Docker的操作是通过命令行界面(CLI)进行的,但也存在一些第三方工具和平台,提供了图形化的用户界面来简化Docker的使用。这些Docke......
  • Docker部署ShowDoc文档工具
    一、ShowDoc介绍1.ShowDoc简介ShowDoc是一个非常适合IT团队的在线API文档、技术文档工具。通过showdoc,你可以方便地使用markdown语法来书写出美观的API文档、数据字典文档、技术文档、在线excel文档等等。2.ShowDoc功能分享与导出响应式网页设计,可将项目文档分享到电脑或移动设......
  • Docker 配置 Wordpress
    1.拉取镜像dockerpullwordpress:latest2.创建存储卷dockervolumecreatewordpress_data3.创建容器dockerrun--namewordpress-chao--restart=always--linkmysql:mysql-p8011:80-d\-vwordpress_data:/var/www/htmlwordpress----外部数据库docker......
  • Docker安装部署
    传统虚拟机技术虚拟机是虚拟出一套硬件,在其上面运行一个完整的操作系统给,例如我们使用KVM,指定系统镜像,然后装系统,最终可以使用,在该系统上再运行所需的应用程序。KVM创建虚拟机是,指定较少的cpu,内存,硬盘等资源,虚拟机性能较低。容器技术容器内的应用程序直接运行再宿主机的内......
  • docker 搭建 rocketmq 本地环境
    如果你还没有安装Docker,请先安装。可以参考官方文档docs.docker.com/install/进行安装NameServernameserver就类似kafka集群中使用的zookeeper负责集群的管理注册和发现。1.拉取镜像dockerpullapache/rocketmq2.创建NameServer容器创建一个新的容器并指定RocketMQ的镜......
  • Docker从入门到部署项目
    Docker概念Docker是一个开源的应用容器引擎,它是基于Go语言并遵从Apache2.0协议开源。Docker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的linux机器上,也可以实现虚拟化。通过容器可以实现方便快速并且与平台解耦的自动化部署方式,无论你部......
  • /var/lib/docker/overlay2/41a765b3cfaa278a67414c5b89234adfdebac7182d4bcd1e7c8a2c6
    现象:Error:Errorresponsefromdaemon:errorcreatingoverlaymountto/var/lib/docker/overlay2/41a765b3cfaa278a67414c5b89234adfdebac7182d4bcd1e7c8a2c6ac250dfb7-init/merged:nosuchfileordirectory原因:由于Docker存储空间中的一些残留文件或损坏的文件系统引......
  • 使用Github Actions构建Docker image
    需求因为网络和性能的原因,有些Dockerfile无法在本地顺利构建。我们可以借助GithubActions每月2000分钟的免费时间来构建。实现在你的仓库里放好Dockerfile如果需要安装文件等操作也一并push建立.github/workflows/main.yml描述任务name:DockerImageCIon:push:......