MySQL之查询性能优化(八)

news2024/12/24 9:06:28

查询性能优化

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);

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GEE训练教程——如何确定几何形状的中心点坐标和相交的坐标

简介 在GEE中,可以使用.geometry()方法来获取几何形状的中心点坐标和相交的坐标。 首先,使用.geometry()方法获取几何形状的几何信息,然后使用.centroid()方法获取几何形状的中心点坐标。示例代码如下: // 获取几何形状的中心点…

ChatGP和kimi对比

使用关于歌手李健的一些问答,进行对比。整体感觉CharGPT更严谨。 ChatGPT kimi [ { “role”: “system”, “content”: “你是 Kimi,由 Moonshot AI 提供的人工智能助手,你更擅长中文和英文的对话。你会为用户提供安全,有帮助…

深度学习与人工智能

深度学习,是一种特殊的人工智能,他与人工智能及机器学习的关系如下: 近些年来,基于人工神经网络的机器学习算法日益盛行起来,逐渐呈现出取代其他机器学习算法的态势,这主要的原因是因为人工神经网络中有一中…

【面试干货】 B 树与 B+ 树的区别

【面试干货】 B 树与 B 树的区别 1、B 树2、 B 树3、 区别与优缺点比较4、 总结 💖The Begin💖点点关注,收藏不迷路💖 在数据库系统中,B 树和 B 树是常见的索引结构,它们在存储和组织数据方面有着不同的设计…

Nginx之正向代理配置示例和说明

一、NGINX正向代理功能简介 Nginx的正向代理功能允许局域网中的客户端通过代理服务器访问Internet资源。具体来说,Nginx作为一种流行的Web服务器和反向代理服务器,在正向代理方面的应用也相当实用。以下是其正向代理功能的几个关键点: 访问外…

【Linux】匿名管道的应用场景 --- 进程池

👦个人主页:Weraphael ✍🏻作者简介:目前正在学习c和算法 ✈️专栏:Linux 🐋 希望大家多多支持,咱一起进步!😁 如果文章有啥瑕疵,希望大佬指点一二 如果文章对…

Makefile:从零开始入门Makefile

目录 1.前言 2.Makefile的简单介绍 3.Makefile中的指令规则 4.Makefile的执行流程 5.Makefile中的变量类型 6.Makefile中的模式匹配 7.Makefile中的函数 8.Makefile补充知识 前言 在Linux中编译CPP文件,我们能够使用GCC命令进行编译,但当项目文件多且繁杂…

OpenGauss数据库-5.数据更新

第1关:插入数据 gsql -d postgres -U gaussdb -W "passwd123123" create table student (id integer primary key,name char(20),age integer ); insert into student values(1,"lily",20),(2,lily,21),(3,marry,19); 第2关:删除数…

C51学习归纳9 --- I2C通讯学习(重点)

首先,我自己学习过以后的直观感觉,通信协议是单片机的灵魂之一,只有规定好了通信协议我们才能够正确的接收到信息,才能实现更加深入的研究。所以这一部分是需要好好学习的。 本节借助一个可存储的芯片AT24C02,进行在I2…

仿饿了么的谁去拿外卖游戏源码

源码介绍 喝酒 没有游戏玩? 懒得下床 不想出去 那么好 这个游戏会 满足你! 玩法 每人都选择一个序号 4 个人为例 张三选第 ① 李四选第 ② 王五选第 ③ 赵前选第 ④ 然后就按 4 下 其中最小的数对应的序号就是他输了就去拿外卖! 源码下载 仿饿了么…

快速开始一个go程序(极简-快速入门)

一、 实验介绍 1.1 实验简介 为了能更高效地使用语言进行编码,Go 语言有自己的哲学和编程习惯。Go 语言的设计者们从编程效率出发设计了这门语言,但又不会丢掉访问底层程序结构的能力。设计者们通过一组最少的关键字、内置的方法和语法,最终…

IDEA创建SpringBoot项目的时候,如何使用Java8,怎么办?

在创建springboot项目的时候,IDEA提示,最低Java版本要求17,但是实际上我们可能不需要这么高的版本,怎么使用Java8呢? 解决办法 修改Server URL地址即可:https://start.aliyun.com

【PHP【实战训练】系统性学习】——最经典的web端头像上传,数据库内容安全精简

👨‍💻个人主页:开发者-曼亿点 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 曼亿点 原创 👨‍💻 收录于专栏&#xff1a…

SpringBoot个人网盘系统-计算机毕业设计源码92922

摘 要 随着科学技术的飞速发展,社会的方方面面、各行各业都在努力与现代的先进技术接轨,通过科技手段来提高自身的优势或改善自身的缺点,互联网的发展文件管理带来了福音。个人网盘系统是以实际运用为开发背景,运用软件工程原理和…

算法——Floyd判圈算法

介绍 Floyd判圈算法用于判断一个链表中是否有环。 思想 使用快慢指针fast, slow,快指针每次走两步fast fast.next.next,慢指针每次走一步slow slow.next。当出现fast null || fast.next null时,说明链表不存在环,如果存在环…

【python报错】关于 xlrd.biffh.XLRDError: Excel xlsx file; not supported 解决方法【已解决】

【Python报错】关于xlrd.biffh.XLRDError: Excel xlsx file; not supported解决方法【已解决】 在使用Python进行数据分析时,经常需要处理Excel文件。xlrd库是一个流行的用于读取Excel文件的库,但如果你在使用xlrd打开.xlsx文件时遇到了xlrd.biffh.XLRDE…

欢乐打地鼠小游戏html源码

这是一款简单的js欢乐打地鼠游戏,挺好玩的,老鼠出来用鼠标点击锤它,击中老鼠获得一积分。 欢乐打地鼠小游戏html源码

信息系统项目管理师0150:工具与技术(9项目范围管理—9.4收集需求—9.4.2工具与技术)

点击查看专栏目录 文章目录 9.4.2 工具与技术9.4.2 工具与技术 专家判断 收集需求过程中,应征求具备如下领域相关专业知识或接受过相关培训的个人或小组的意见,涉及的领域包括:可行性研究与评估;需求获取;需求分析;需求文件;以往类似项目的项目需求;图解技术;引导;冲…

这个国际档案日,大比武放榜、直播预约、课件下载,一样都不能少!

关注我们 - 数字罗塞塔计划 - 2024年6月9日第十七个国际档案日来临,数字罗塞塔计划放大招:第二届大比武活动榜单揭晓、ARCHE-2024上海智慧档案高峰论坛直播预约、2024上半年度课件大礼包下载。如此大礼,岂能错过? PART.01 榜单…

通过 Python+Nacos实现微服务,细解微服务架构

shigen坚持更新文章的博客写手,擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长,分享认知,留住感动。 个人IP:shigen 背景 一直以来的想法比较多,然后就用Python编写各种代码脚本。很多…