MySQL之查询性能优化(八)

查询性能优化

MySQL查询优化器的局限性

MySQL的万能"嵌套循环"并不是对每种查询都是最优的。不过还好,MySQL查询优化器只对少部分查询不适用,而且我们往往可以通过改写查询让MySQL高效地完成工作。还有一个好消息,MySQL5.6版本正式发布后,会消除很多MySQL原本的限制,让更多的查询能够以尽可能高的效率完成。

关联子查询

MySQL的子查询实现得非常糟糕。最糟糕的一类查询是WHERE条件中包含IN()的子查询语句。例如,我们希望找到Sakila数据库中,演员Penelope Guinness(他的actor_id为1)参演过的所有影片信息。很自然的,我们会按照下面的方式用子查询实现:

mysql> SELECT * FROM sakila.film WHERE film_id IN(SELECT film_id FROM sakila.film_actor WHERE actor_id =1);

因为MySQL对IN()列表的选项有专门的优化策略,一般会认为MySQL会先执行子查询返回所有包含actor_id为1的film_id。一般来说,IN()列表查询速度很快,所以我们会认为上面的查询会这样执行:

-- SELECT GROUP_CONCAT(film_id) FROM sakila.film_actor WHERE actor_id=1;
-- Result :1,23,25,106,140,166,277,361,438,499,506,509,605,635,749,832,939,970,980
SELECT * FROM sakila.film WHERE film_id IN(1,23.....................,980);

很不幸,MySQL不是这样做的。MySQL会讲相关的外层表压到子查询中,它认为这样可以更高效率地查找到数据行。也就是说,MySQL会将查询改写成下面的样子:

SELECT * FROM sakila.film WHERE EXISTS (SELECT * FROM sakila.film_actor WHERE actor_id = 1 AND film_actor.film_id = film.film_id)

这时,子查询需要根据film_id来关联外部表film,因为需要film_id字段,所以MySQL认为无法先执行这个查询。通过EXPLAIN可以看到子查询是一个相关子查询(DEPENDENT SUBQUERY)(可以使用EXPLAIN EXTENDED来查看这个查询被改写成了什么样子)


mysql> EXPLAIN SELECT * FROM sakila.film WHERE EXISTS (SELECT * FROM sakila.film_actor WHERE actor_id = 1 AND film_actor.film_id = film.film_id)
    -> ;
+----+--------------------+------------+------------+--------+------------------------+---------+---------+---------------------------+------+----------+-------------+
| id | select_type        | table      | partitions | type   | possible_keys          | key     | key_len | ref                       | rows | filtered | Extra       |
+----+--------------------+------------+------------+--------+------------------------+---------+---------+---------------------------+------+----------+-------------+
|  1 | PRIMARY            | film       | NULL       | ALL    | NULL                   | NULL    | NULL    | NULL                      | 1000 |   100.00 | Using where |
|  2 | DEPENDENT SUBQUERY | film_actor | NULL       | eq_ref | PRIMARY,idx_fk_film_id | PRIMARY | 4       | const,sakila.film.film_id |    1 |   100.00 | Using index |
+----+--------------------+------------+------------+--------+------------------------+---------+---------+---------------------------+------+----------+-------------+
2 rows in set (0.10 sec)

根据EXPLAIN的输出我们可以看到,MySQL先选择对flim表进行全表扫描,然后根据返回的film_id逐个进行子查询。如果是一个很小的表,这个查询的糟糕的性能可能还不会引起注意,但是如果外层的表是一个非常大的表,那么这个查询的性能会非常糟糕。当然我们很容易用下面的办法来重写这个查询:

mysql>SELECT  film.* FROM sakila.film INNER JOIN sakila.film_actor USING(film_id) WHERE actor_id = 1;

另一个优化的办法是使用函数GROUP_CONCAT()在IN()中构造一个由逗号分割的列表,有时这比上面的使用关联改写更快。因为使用IN()加子查询,性能经常会非常糟,所以通常建议使用EXISTS()等效的改写查询来获取更好的效率。下面是另一种改写IN()加子查询的办法:

mysql>SELECT * FROM sakila.film WHERE EXISTS (SELECT * FROM sakila.film_actor WHERE actor_id = 1 AND film_actor.film_id = film.film_id)

如何用好关联子查询

并不是所有关联子查询的性能都回很差。如果有人跟你说:“别用关联子查询”,那么不要理他。先测试,然后做出自己的判断。很多时候关联子查询是一种非常合理、自然,甚至是性能最好的写法,看看下面的例子:

mysql> EXPLAIN SELECT film_id,language_id FROM sakila.film
    -> WHERE NOT EXISTS(SELECT * FROM sakila.film_actor WHERE film_actor.film_id=film.film_id)\G
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: film
   partitions: NULL
         type: index
possible_keys: NULL
          key: idx_fk_language_id
      key_len: 1
          ref: NULL
         rows: 1000
     filtered: 100.00
        Extra: Using where; Using index
*************************** 2. row ***************************
           id: 2
  select_type: DEPENDENT SUBQUERY
        table: film_actor
   partitions: NULL
         type: ref
possible_keys: 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
2 rows in set, 2 warnings (0.00 sec)

一般回建议使用左外连接(LEFT OUTER JOIN)重写该查询,以代替子查询。理论上,改写后MySQL的执行计划完全不会改变。我们来看这个例子

mysql> EXPLAIN SELECT film.film_id,film.language_id
    -> FROM sakila.film
    -> LEFT OUTER JOIN sakila.film_actor USING(film_id)
    -> WHERE film_actor.film_id IS NULL\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: film
   partitions: NULL
         type: index
possible_keys: NULL
          key: idx_fk_language_id
      key_len: 1
          ref: NULL
         rows: 1000
     filtered: 100.00
        Extra: Using index
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: film_actor
   partitions: NULL
         type: ref
possible_keys: idx_fk_film_id
          key: idx_fk_film_id
      key_len: 2
          ref: sakila.film.film_id
         rows: 5
     filtered: 100.00
        Extra: Using where; Not exists; Using index
2 rows in set, 1 warning (0.00 sec)

可以看到,这里的执行计划基本上是一样,下面是一些微小的区别:

  • 1.表film_actor的访问类型是一个DEPENDENT SUBQUERY,而另一个是SIMPLE.这个不同是由于语句的写法不同导致的,一个是普通查询,一个是子查询。这对底层存储引擎接口来说,没有任何不同
  • 2.对film表,第二个查询的Extra中没有"Using where",但这并不重要,第二个查询的USING子句和第一个查询的WHERE子句实际上是完全一样的。
  • 3.在第二个表film_actor的执行计划的Extra列有"Not exists"。这是前面提到的提前终止算法(early-termination algorithm),MySQL通过使用"Not exists"优化来避免在表film_actor的索引中读取任何额外的行。这完全等效于直接编写NOT EXISTS子查询,这个执行计划中也是一样,一旦匹配到一行数据,就立刻停止扫描

所以,从理论上来讲,MySQL将使用完全相同的执行计划来完成这个查询。现实世界中,建议通过一些测试来判断使用哪种写法速度会更快。针对上面的案例,测试结果也是不同的,如表所示在这里插入图片描述
.测试结果显示,使用子查询的写法要略微慢些!不过每个具体的案例会各有不同,有时候子查询写法也会快些。例如,当返回结果中只有一个表中的某些列的时候。听起来,这种情况对于关联查询效率也会更好。具体情况具体分析,例如下面的关联,我们希望返回所有演员参演的电影,因为一个电影会有很多演员参演,所以可能会返回一些重复的记录:

mysql> SELECT film.film_id FROM sakila.film INNER JOIN sakila.film_actor USING(film_id);

我们需要使用DISTINCT和GROUP BY来移除重复的记录:

mysql> SELECT DISTINCT film.film_id FROM sakila.film INNER JOIN sakila.film_actor USING(film_id);

但是,回头看看这个查询,到底这个查询返回的结果集意义是什么?至少这样的写法回访SQL的意义很不明显。如果使用EXISTS则很容易表达"有演员参演"的逻辑,而且不需要使用DISTINCT和GROUP BY,也不会产生重复的结果集,我们知道一旦使用了DISTINCT和GROUP BY,那么在查询的执行过程中,通常需要产生临时中间表。下面我们用子查询的写法替换上面的关联:

mysql> SELECT film_id FROM sakila.film WHERE EXISTS(SELECT * FROM sakila.film_actor WHERE film.film_id = film_actor.film_id);

再一次,我们需要通过测试来比对这两种写法,哪个更快一些,测试结果如表所示.在这个案例中,我们看到子查询速度要比关联查询更快些。通过上面这个案例,主要想说明两点:一时不需要听取那些关于子查询的"绝对真理",二十应该用测试来验证对子查询的执行计划和相应时间的假设。我们应该通过测试来验证猜想在这里插入图片描述

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

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

相关文章

JavaWeb3 Ajax+Axios+Element+Nginx部署

Ajax 异步JS和XML 1.数据交换&#xff1a;给服务器发送请求&#xff0c;并获取服务器相应的数据 2.异步交互&#xff1a;在不重新加载整个页面的情况下&#xff0c;与服务器交换数据并更新部分网页 同步与异步 原生Ajax <!DOCTYPE html> <html> <body><…

德国80%的统计学教授都会答错的6个与P值有关的问题!

小编阅读了一篇发表于2002年关于P值的一项问卷调查研究 [1]&#xff0c;作者在6所德国大学中邀请了3组不同的受试者&#xff0c;分别为: 心理学专业的学生(n 44)&#xff1b;主要从事科学研究但不进行统计相关教学的教授和讲师(n 39)&#xff1b;进行统计相关教学的教授和讲师…

vs2022专业版永久密钥

vs2022专业版永久密钥&#xff1a; vs2022专业版永久密钥&#xff1a; Visual Studio 2022 Enterprise&#xff1a;VHF9H-NXBBB-638P6-6JHCY-88JWH Visual Studio 2022 Professional&#xff1a;TD244-P4NB7-YQ6XK-Y8MMM-YWV2J

大模型高级 RAG 检索策略之混合检索

古人云&#xff1a;兼听则明&#xff0c;偏信则暗&#xff0c;意思是要同时听取各方面的意见&#xff0c;才能正确认识事物&#xff0c;只相信单方面的话&#xff0c;必然会犯片面性的错误。 在 RAG&#xff08;Retrieval Augmented Generation&#xff09;应用中也是如此&…

UE Editor API 整理

UE Editor API 整理 过一下 https://github.com/20tab/UnrealEnginePython/blob/master/docs/&#xff0c;熟悉一下编辑器 API&#xff0c;方便后续编辑器脚本开发 后续的目标是所有编辑器操作应该都可以脚本化&#xff08;自动化&#xff09;&#xff0c;这样把 GPT 接进 UE…

WEB-Wordlist-Generator:为扫描后的Web应用生成相关联的字典

关于WEB-Wordlist-Generator WEB-Wordlist-Generator是一款功能强大的字典生成工具&#xff0c;该工具旨在帮助广大研究人员扫描目标Web应用程序并生成与之相关联的字典文件&#xff0c;从而允许我们对相关的网络威胁行为执行预备性应对策略。 功能介绍 当前版本的WEB-Wordli…

CAN总线学习笔记-CAN帧结构

数据帧 数据帧&#xff1a;发送设备主动发送数据&#xff08;广播式&#xff09; 标准格式的11ID不够用了&#xff0c;由此产生了扩展格式 SOF&#xff1a;帧起始&#xff0c;表示后面一段波形为传输的数据位 ID&#xff1a;标识符&#xff0c;区分功能&#xff0c;同时决定优…

旭日X3与英伟达Orin NX通过TCP传输图片

观前提醒&#xff1a;本文主要内容为使用Python在局域网内建立TCP连接并传输图片信息&#xff0c;计算机为一块旭日X3和一块英伟达Orin NX。 一、什么是TCP TCP&#xff08;传输控制协议&#xff09;是一种可靠的、面向连接的协议&#xff0c;它确保数据包的顺序传输和完整性…

工厂设备数采对接数字化平台

在数字化转型的浪潮下&#xff0c;工厂设备数据采集与数字化平台对接已成为提升生产效率、降低运维成本的关键手段。HiWoo Cloud作为工业物联网软件平台&#xff0c;以其卓越的性能和丰富的功能&#xff0c;为众多企业提供了全面、高效的设备数采对接数字化平台解决方案。本文将…

hutool工具实践-缓存

简介 依赖引入 <dependency><groupId>cn.hutool</groupId><artifactId>hutool-cache</artifactId><version>5.8.17</version></dependency> hutool工具既可以像上一章hutool工具实践-验证码-CSDN博客所说直接全部引入&#x…

用idea将java文件打成jar包

一、用idea将java文件打成jar包 1、在idea上选择file—Project Structure 2、Artifacts —点–JAR—From modules with dependencies 3、选择要打包的java文件 4、Build — Build Artifacts 5、找到刚才添加的Artifacts直接Build 6、生成jar包文件

钢轨行业的数字化转型:全生命周期管理与智能化决策支持

引言 随着时代的发展和技术的进步&#xff0c;数字化转型已经成为各行各业实现创新和提升竞争力的重要途径。在钢轨行业&#xff0c;数字化转型不仅是跟上时代潮流的必然选择&#xff0c;更是推动行业发展、提高效率和保障安全的关键举措。 钢轨作为铁路交通的基础设施之一&am…

余承东的“狂”,李想学不会

理想MEGA失利的连锁反应还在继续&#xff0c;李想第三次下调了今年的销量目标。 今年早些时候&#xff0c;李想还踌躇满志&#xff0c;提出今年销量目标为80万辆&#xff0c;这要比2023年37.6万辆的成绩翻了一倍不止,还喊出实现“中国市场豪华汽车品牌销量第一”的宣言。3月初…

ES6真题合集(一)

ES6真题合集&#xff08;一&#xff09; 1. var、let、const之间的区别2. ES6中数组新增了哪些扩展2.1 扩展运算符2.2 Array.from() 方法2.3 Array.of() 方法2.4 find() 和 findIndex() 方法2.5 箭头函数2.6 模板字符串 3. ES6中对象新增了哪些扩展3.1 属性的简写3.2 属性名表达…

ssh远程管理yum源进阶

文章目录 sshNFS 共享存储服务实验yum的进阶使用Apanche做一个网页形式的源用vsftpd做一个源混合源 ssh ssh是一种安全通道协议&#xff0c;用来实现字符界面的远程登录&#xff0c;远程复制&#xff0c;远程文本传输 ssh对通信双方的数据进行了加密 用户名和密码登录 秘钥…

GitHub最大的开源算法库

GitHub 上最大的开源算法库 The Algorithms&#xff0c;值得每位算法工程师收藏&#xff01; 该库收录了 Python、Java、C、JavaScript、Go 等多种主流编程语言的算法实现代码。 其中包含二分查找、快速排序、斐波那契数列等众多热门算法&#xff0c;可以说是应用尽有。 此外&a…

关于vue2 antd 碰到的问题总结下

1.关于vue2 antd 视图更新问题 1.一种强制更新 Vue2是通过用Object…defineProperty来设置数据的getter和setter实现对数据和以及视图改变的监听的。对于数组和对象这种引用类型来说&#xff0c;getter和setter无法检测到它们内部的变化。用这种 this.$set(this.form, "…

【问题复盘】第三方接口变慢导致服务崩溃

一、事件经过 -1、一个不在公司的下午&#xff0c;接到客户投诉&#xff0c;说平台不能访问了。 0、介入调查&#xff0c;发现服务器http请求无法访问&#xff0c;https请求却可以正常访问&#xff0c;一时有些无法理解&#xff1b;&#xff08;后来发现&#xff0c;http和htt…

CMMI软件能力成熟度评估标准

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl CMMI概述 CMMI&#xff0c;全称为Capability Maturity Model Integration&#xff0c;即能力成熟度模型集成&#xff0c;是在原有的CMM&#xff08;Capability Maturity Mo…

C语言小例程8/100

题目&#xff1a;输出特殊图案&#xff0c;请在c环境中运行&#xff0c;看一看 程序分析&#xff1a;字符共有256个。不同字符&#xff0c;图形不一样。 #include<stdio.h> int main() {char a176,b219;printf("%c%c%c%c%c\n",b,a,a,a,b);printf("%c%c%c…