MySQL之查询性能优化(六)

查询性能优化

查询优化器

  • 9.等值传播
    如果两个列的值通过等式关联,那么MySQL能够把其中一个列的WHERE条件传递到另一列上。例如,我们看下面的查询:
mysql> SELECT film.film_id FROM film
    -> INNER JOIN film_actor USING(film_id)
    -> WHERE film_id > 500;

因为这里使用了film_id字段进行等值关联,MySQL知道这里的WHERE子句不仅适用于film表,而且对于film_actor表同样适用。如果适用的是其他的数据库管理系统,可能还需要手动通过一些条件来告知优化器这个WHERE条件适用于两个表,那么写法就会如下:

... WHERE film.film_id > 500 AND film_actor.film_id > 500

在MySQL中这是不必要的,这样写反而会让查询更难维护。

  • 10.列表IN()的比较
    在很多数据库系统中,IN()完全等同于多个OR条件的子句,因为这两者是完全等价的。在MySQL中这点是不成立的,MySQL将IN()列表中的数据先进行排序,然后通过二分查找的方式来确定列表中的值是否满足条件,这是一个O(logn)复杂度的操作,等价地转换成OR查询的复杂度为O(n),对于IN()列表中有大量取值的时候,MySQL的处理速度将会更快。

上面列举的远不是MySQL优化器的全部,MySQL还会做大量其他的优化。上面的例子已经足以说明优化器的复杂性和智能性了。如果说从上面的讨论中应该学到什么,那就是"不要自以为比优化器更聪明"。最终你可能会占点便宜,但是更有可能会使查询变得更加复杂而难以维护,而最终的收益却未零。让优化器按照它的方式工作就可以了。当然,虽然优化器已经很智能了,但是有时候也无法给出最优的结果。有时候你可能比优化器更了解数据,例如,由于应用逻辑使得某些条件总是成立;还有时,优化器缺少某种功能特性,如哈希索引;再如前面提到的,从优化器的执行成本角度评估出来的最优执行计划,实际运行中可能比其他的执行计划更慢。如果能够确认优化器给出的不是最佳选择,并且清楚背后的原理,那么也可以帮助优化器做进一步的优化。例如,可以在查询中添加hint提示,也可以重写查询,或者重新设计更优的库表结构,或者添加更合适的索引

数据和索引的统计信息

在这里插入图片描述

重新回忆一下MySQL的架构,MySQL架构由多个层次组成。在服务器层有查询优化器,却没有保存数据和索引的统计信息。统计信息由存储引擎实现,不同的存储引擎可能会存储不同的统计信息(也可以按照不同的格式存储统计信息)。某些引擎,例如Archive引擎,则根本没有存储任何统计信息。因为服务器层没有任务统计信息,所以MySQL查询优化器在生成查询的执行计划时,需要向存储引擎获取相应的统计信息。存储引擎则提供给优化器对应的统计信息,包括:每个表或者索引有多少个页面、每个表的每个索引的基数是多少、数据行和索引长度、索引的分布信息等。优化器根据这些信息来选择一个最优的执行计划。

MySQL如何执行关联查询

MySQL中"关联"(join)一次所包含的意义比一般意义上理解的要更广泛。总的来说,MySQL认为任何一个查询都是一次"关联"——并不仅仅是一个查询需要到两个表匹配才叫关联,所以在MySQL中,每一个查询,每一个片段(包括子查询,甚至基于单表的SELECT)都可能是关联。所以,理解MySQL如何执行关联查询至关重要。我们先来看一个UNION查询的例子,对于UNION查询,MySQL先将一系列的单个查询结果放到一个临时表中,然后再重新读出临时表数据来完成UNION查询。在MySQL的概念中,每个查询都是一次关联,所以读取结果临时表也是一次关联。当前MySQL关联执行的策略很简单:MySQL对任何关联都执行嵌套循环关联操作,即MySQL先在一个表中循环取出单条数据,然后再嵌套循环到下一个表中寻找匹配的行,依次下去,知道找到所有表中匹配的行为止。然后根据各个表匹配的行,返回查询中需要的各个列。MySQL会尝试在最后一个关联表中找到所有匹配的行,如果最后一个关联表无法找到更多的行以后,MySQL返回上一层次关联表,看是否能够找到更多的匹配记录,依此类推迭代执行。按照这样的方式查找第一个表记录,再嵌套查询下一个关联表,然后回溯到上一个表,在MySQL中是通过嵌套循环的方式实现——正如其名"嵌套循环关联"。请看下面的例子中的简单查询:

SELECT
tbl1.col1,
tbl2.col2
FROM
tbl1
INNER JOIN tbl2 USING ( col3 )
WHERE
tbl1.col1 IN (5,6)

假设MySQL按照查询中的表顺序进行关联,我们则可以用下面的伪代码表示MySQL将如何完成这个查询:

outer_iter = iterator over tbl1 where col1 IN (5,6)
outer_row = outer_iter.next
while outer_row
inner_iter = iterator over tbl2 where col3 = outer_row.col3
inner_row = inner_iter.next
while inner_now
output [outer_row.col1, inner_row.col2]
inner_row = inner_iter.next
end

outer_row = outer_iter.next
end

上面的执行计划对于单表查询和多表关联查询都适用,如果是一个单表查询,那么只需完成上面外层的基本操作。对于外连接上面的执行过程仍然适用。例如,我们将上面查询修改如下:

SELECT
tbl1.col1,
tbl2.col2
FROM
tbl1
LEFT OUTER JOIN tbl2 USING ( col3 )
WHERE
tbl1.col1 IN (5,6)

对应的伪代码如下,

outer_iter = iterator over tbl1 where col1 IN (5,6)
outer_row = outer_iter.next
while outer_row
inner_iter = iterator over tbl2 where col3 = outer_row.col3
inner_row = inner_iter.next
if inner_row
while inner_row
output [outer_row.col1, inner_row.col2]
inner_row = inner_iter.next
end
else
output [outer_row.col1, NULL]
end
outer_row = outer_iter.next
end

在这里插入图片描述

另一种可视化查询执行计划的方法是根据优化器执行的路径绘制出对应的"泳道图"。如图所示,绘制了前面示例中内连接的泳道图。如图所示,从本质上说,MySQL对所有的类型的查询都以同样的方式运行。例如,MySQL在FROM子句中遇到子查询时,先执行子查询并将其结果放到一个临时表中(MySQL的临时表是没有任何索引的,在编写复杂的子查询和关联查询的时候需要注意这一点。这一点对UNION查询也一样)。然后将这个临时表当作一个普通表对待(正如其名"派生表")。MySQL在执行UNION查询时也使用类似的临时表,在遇到右外连接的时候,MySQL将其改写成等价的左外连接。简而言之,当前版本的MySQL会将所有的查询类型都转换成类似的执行计划。不过,不是所有的查询都可以转换成上面的形式。例如,全外连接就无法通过嵌套玄幻和回溯的方式完成,这是当发现关联表中没有找到任何匹配行的时候,则可能是因为关联是恰好从一个没有任何匹配的表开始。这大概也是MySQL并不支持全外连接的原因,还有些场景,虽然可以转换成嵌套循环的方式,但是效率却非常差。

执行计划

在这里插入图片描述

在这里插入图片描述

和很多其他关系数据库不同,MySQL并不会生成查询字节码执行查询。MySQL生成查询的一棵指令书,然后通过存储引擎执行完成这棵指令树并返回结果。最终的执行计划包含了重构查询的全部信息。如果对某个查询执行EXPLAIN EXTENDED后,再执行SHOW WARNINGS,就可以看到重构出的查询。(MySQL根据执行计划生成输出。这和原查询有完全相同的语义,但是查询语句可能并不完全相同)。任何多表查询都可以使用一棵树表示,例如,可以按照如图所示执行一个四表的关联操作.在计算机科学中,这被成为一棵平衡树。但是,这并不是MySQL执行查询的方式。正如前面提到的,MySQL总是会从一个表开始一直嵌套循环、回溯完成所有表关联。所以,MySQL的执行计划总是如图所示,是一棵左侧深度优先的树

关联查询优化器

MySQL有优化器最重要的一部分就是关联查询优化,它决定了多个表关联时的顺序。通常多表关联的时候,可以有多种不同的关联顺序来获得相同的执行结果。关联查询优化器则通过评估不同顺序时的成本来选择一个代价最小的关联顺序。下面的查询可以通过不同顺序的关联最后都获得相同的结果:

SELECT
film.film_id,
film.title,
film.release_year,
actor.actor_id,
actor.first_name,
actor.last_name
FROM
sakila.film
INNER JOIN sakila.film_actor USING ( film_id )
INNER JOIN sakila.actor USING ( actor_id );

容易看出,可以通过一些不同的执行计划来完成上面的查询。例如,MySQL可以从film表开始,使用film_actor表的索引film_id来查找对应的actor_id值,然后再根据actor表的主键找到对应的记录。Oracle用户会用下面的术语描述:“film表作为驱动表先查找film_actor表,然后以此结果为驱动表再查找actor表”。这样做效率应该会不错,我们再使用EXPLAIN 看看MySQL将如何执行这个查询:

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: actor
   partitions: NULL
         type: ALL
possible_keys: PRIMARY
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 200
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: film_actor
   partitions: NULL
         type: ref
possible_keys: PRIMARY,idx_fk_film_id
          key: PRIMARY
      key_len: 2
          ref: sakila.actor.actor_id
         rows: 27
     filtered: 100.00
        Extra: Using index
*************************** 3. row ***************************
           id: 1
  select_type: SIMPLE
        table: film
   partitions: NULL
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 2
          ref: sakila.film_actor.film_id
         rows: 1
     filtered: 100.00
        Extra: NULL
3 rows in set, 1 warning (0.00 sec)

这和我们前面给出的执行计划完全不同。MySQL从actor表开始(我们从上面的EXPLAIN 结果的第一行输出可以看出这点)。然后与我们前面的计划按照相反的顺序进行关联。这样是否效率更高呢?我们来看看,我们先使用STRIGHT_JOIN关键字,按照我们之前的顺序执行,这里是对应的EXPLAIN输出结果:

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: film
   partitions: NULL
         type: ALL
possible_keys: PRIMARY
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1000
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: film_actor
   partitions: NULL
         type: ref
possible_keys: PRIMARY,idx_fk_film_id
          key: idx_fk_film_id
      key_len: 2
          ref: sakila.film.film_id
         rows: 5
     filtered: 100.00
        Extra: Using index
*************************** 3. row ***************************
           id: 1
  select_type: SIMPLE
        table: actor
   partitions: NULL
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 2
          ref: sakila.film_actor.actor_id
         rows: 1
     filtered: 100.00
        Extra: NULL
3 rows in set, 1 warning (0.00 sec)

我们来分析一下为什么MySQL会将关联顺序倒转过来:可以看到,关联顺序倒转后的第一个关联表只需要扫描很少的行数(严格来说,MySQL并不根据读取的记录来选择最优的执行计划。实际上MySQL通过预估需要读取的数据页来选择,读取的数据页越少越好。不过读取的记录数通常能够很好地反应一个查询的成本)。在两种关联顺序下,第二个和第三个关联表都是根据索引查询,速度都很快,不同地是需要扫描的索引项的数量是不同的:

  • 1.将film表作为第一个关联表时,会找到1000条记录,然后对film_actor和actor表进行嵌套循环查询
  • 2.如果MySQL选择首先扫描actor表,只会返回200条记录进行后面的嵌套循环查询。

换句话说,倒转的关联顺序会让查询进行跟梢的嵌套循环和回溯操作。为了验证优化器的选择是否正确,我们单独执行两个查询,并且看看对应的Last_query_cost状态值。我们看到倒转的关联顺序的预估成本为241,而原来的查询的预估成本为1154.
这个简单的例子主要想说明MySQL是如何选择合适的关联顺序来让查询执行的成本尽可能的低。重新定义关联的顺序是优化器非常重要的一部分功能。不过有的时候,优化器给出的并不是最优的关联顺序。这时可以使用STRAIGHT_JOIN关键字重写查询,让优化器按照你认为的最优的关联顺序执行——不过老实说,人的判断很难那么精准。绝大多数时候,优化器做出的选择都比普通人的判断要更准确。关联优化器会尝试在所有的关联顺序中选择一个成本最小的来生成执行计划树。如果可能,优化器会遍历每一个表然后逐个做嵌套循环计算每一棵可能的执行计划树的成本,最后返回一个最优的执行计划。
不过,糟糕的是,如果有超过N个表的关联,那么需要检查N的阶乘种关联顺序。我们称之为所有可能的执行计划的"搜索空间",搜索空间的增长速度非常快——例如,若是10个表的关联,那么共有3 628 800种不同的关联顺序!当搜索空间非常大的时候,优化器不可能注意评估每一种关联顺序的成本。这时,优化器选择使用"贪婪"搜索的方式查找"最优"的关联顺序。实际上,当需要关联的表超过optmizer_search_depth的限制的时候,就会选择"贪婪"搜索模式了。在MySQL这些年的发展过程重,优化器积累了很多"启发式"的优化策略来加速执行计划的生成。绝大多数情况下,这都是有效地,但因为不会去计算每一种关联顺序的成本,所以偶尔也会选择一个不是最优的执行计划。有时,各个查询的顺序并不能随意安排,这时关联优化器可以根据这些规则大大减少搜索空间,例如,左连接、相关子查询(后面将讨论子查询)。这是因为后面的表的查询需要依赖于前面表的查询结果。这种依赖关系通常可以帮助优化器大大减少需要扫描的的执行计划数量

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/682308.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

K8S==ingress配置自签名证书

安装openssl Win32/Win64 OpenSSL Installer for Windows - Shining Light Productions 生成证书 openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout example.local.key -out example.local.crt -subj "/CNexample.local/Oexample.local"创建K8S secr…

揭秘c语言储存类别

前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文将整理c语言的储存类型的知识点 储存类型概念 描述:用于解决内存开辟与解放的时间的问题。跟作用域没啥关系。 但是呢,他也是能影响到程序的运行的,所以是很关键的。 类型: auto :自…

htb_solarlab

端口扫描 80,445 子域名扫描 木有 尝试使用smbclient连接445端口 Documents目录可查看 将Documents底下的文件下载到本地看看 xlsx文件里有一大串用户信息,包括username和password 先弄下来 不知道在哪登录,也没有子域名,于是返回进行全端…

flyfish3.0.0配置避坑

1.基础环境准备篇 doc/01-基础环境准备篇.md 云智慧/FlyFish - Gitee.com 使用教程里给出的java环境时,可以显示java版本,但是不能显示Maven的版本 改为: export NODE_HOME/usr/local/node/node-v14.19.3-linux-x64 export PATH$NODE_HOME…

C语言—字符函数和字符串函数

1.字符分类函数 C语言中有一系列的函数是专门做字符分类的,也就是一个字符是属于什么类型的字符的。 这些函数的使用都需要包含一个头文件 ctype.h。 例:将一句话中的小写字母改成大写字母。 2.字符转换函数 头文件:ctype.h C语言提供了2…

不是,有了这套IP地址管理开源系统谁还用Excel啊

号主:老杨丨11年资深网络工程师,更多网工提升干货,请关注公众号:网络工程师俱乐部 中午好,我的网工朋友。 作为网工的我们想必都很清楚IP地址管理的重要性以及其复杂性,传统的Excel表格虽然在某些情况下能…

搭建大型分布式服务(三十九)SpringBoot 整合多个kafka数据源-支持Aware模式

系列文章目录 文章目录 系列文章目录前言一、本文要点二、开发环境三、原项目四、修改项目五、测试一下五、小结 前言 本插件稳定运行上百个kafka项目&#xff0c;每天处理上亿级的数据的精简小插件&#xff0c;快速上手。 <dependency><groupId>io.github.vipjo…

YOLOv8+PyQt5苹果叶病害检测(可以重新训练,yolov8模型,从图像、视频和摄像头三种路径识别检测)

效果视频&#xff1a;YOLOv8PyQt5苹果叶病害检测系统完整资源集合 资源包含可视化的苹果叶病害检测系统&#xff0c;基于最新的YOLOv8训练的苹果叶病害检测模型&#xff0c;和基于PyQt5制作的可视苹果叶病害系统&#xff0c;包含登陆页面和检测页面&#xff0c;该系统可自动检…

上位机图像处理和嵌入式模块部署(f407 mcu中的单独上位机烧录方法)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们说过&#xff0c;stm32有三种烧录方法&#xff0c;一种是st-link v2&#xff0c;一种是dap&#xff0c;一种是j-link。不过我们在实际操作…

电阻、电容和电感测试仪设计

在现代化生产、学习、实验当中,往往需要对某个元器件的具体参数进行测量,在这之中万用表以其简单易用,功耗低等优点被大多数人所选择使用。然而万用表有一定的局限性,比如:不能够测量电感,而且容量稍大的电容也显得无能为力。所以制作一个简单易用的电抗元器件测量仪是很…

17K star,一款开源免费的手机电脑无缝同屏软件

导读&#xff1a;白茶清欢无别事&#xff0c;我在等风也等你。 作为程序员&#xff0c;在我们的工作中经常需要把手机投票到电脑进行调试工作&#xff0c;选择一款功能强大的投屏软件是一件很必要的事情。今天给大家介绍一款开源且免费的投屏软件&#xff0c;极限投屏&#xff…

json和axion结合

目录 java中使用JSON对象 在pom.xml中导入依赖 使用 public static String toJSONString(Object object)把自定义对象变成JSON对象 json和axios综合案例 使用的过滤器 前端代码 响应和请求都是普通字符串 和 请求时普通字符串&#xff0c;响应是json字符串 响应的数据是…

postman教程-14-生成随机数

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节我们学习了Postman关联接口的调用方法&#xff0c;本小节我们讲解一下Postman生成随机数的方法。 在接口测试中&#xff0c;经常需要向接口发送不同的输入数据&#xff0c;以确保接口的健壮性和可靠性。…

Vue3实战笔记(51)—Vue 3封装带均线的k线图

文章目录 前言带均线的k线图总结 前言 继续封装一个封装带均线的k线图 带均线的k线图 EChartsCandlestickSh.vue&#xff1a; <template><div ref"chartContainer" style"width: 100%; height: 500px"></div></template><scr…

HackTheBox-Machines--SolidState

SolidState 测试过程 1 信息收集 NMAP ┌──(root㉿serven)-[~] └─# nmap -p 0-65535 -A 10.129.224.177 Starting Nmap 7.92 ( https://nmap.org ) at 2024-06-05 00:52 CST Host is up (0.063s latency). Not shown: 65530 closed tcp ports (reset) PORT STATE SE…

如何解决 Zabbix模板同步超时:解决运维技术领域的BugFailed to sync Zabbix template due to timeout

如何解决 Zabbix模板同步超时&#xff1a;解决运维技术领域的BugFailed to sync Zabbix template due to timeout 原创作者&#xff1a; 猫头虎 作者微信号&#xff1a; Libin9iOak 作者公众号&#xff1a; 猫头虎技术团队 更新日期&#xff1a; 2024年6月6日 博主猫头虎…

python学习笔记-05

函数 基本上所有的高级语言都支持函数&#xff0c;函数就是一种代码抽象的方式。之前所使用的len、print等都是python的内置函数。 1.初识函数 在编写程序过程中&#xff0c;如果一段代码经常出现&#xff0c;为了提高编写效率&#xff0c;将这类实现某个功能的代码作为一个…

Linux: ubi rootfs 加载故障案例

文章目录 1. 前言2. ubi rootfs 加载故障现场3. 故障分析与解决4. 参考资料 1. 前言 限于作者能力水平&#xff0c;本文可能存在谬误&#xff0c;因此而给读者带来的损失&#xff0c;作者不做任何承诺。 2. ubi rootfs 加载故障现场 问题故障内核日志如下&#xff1a; Star…

前后端分离与实现 ajax 异步请求 和动态网页局部生成

前端 <!DOCTYPE html><!-- 来源 --> <!-- https://cloud.tencent.com/developer/article/1705089 --> <!-- https://geek-docs.com/ajax/ajax-questions/19_ajax_javascript_send_json_object_with_ajax.html --> <!-- 配合java后端可以监听 --&…

Jmeter的几种参数化方式

1.为什么要做参数化&#xff1f; 在用jmeter脚本进行压测的时候&#xff0c;为了更真实的模拟起到更好的效果&#xff0c;我们需要让参数动态变化起来&#xff0c;也就是参数化。通过参数化我们也可以更好、更灵活的维护我们的测试脚本。 2.参数化的方式 能够实现参数化的方式有…