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: 1select_type: PRIMARYtable: filmpartitions: NULLtype: index
possible_keys: NULLkey: idx_fk_language_idkey_len: 1ref: NULLrows: 1000filtered: 100.00Extra: Using where; Using index
*************************** 2. row ***************************id: 2select_type: DEPENDENT SUBQUERYtable: film_actorpartitions: NULLtype: ref
possible_keys: idx_fk_film_idkey: idx_fk_film_idkey_len: 2ref: sakila.film.film_idrows: 5filtered: 100.00Extra: 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: 1select_type: SIMPLEtable: filmpartitions: NULLtype: index
possible_keys: NULLkey: idx_fk_language_idkey_len: 1ref: NULLrows: 1000filtered: 100.00Extra: Using index
*************************** 2. row ***************************id: 1select_type: SIMPLEtable: film_actorpartitions: NULLtype: ref
possible_keys: idx_fk_film_idkey: idx_fk_film_idkey_len: 2ref: sakila.film.film_idrows: 5filtered: 100.00Extra: 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.mzph.cn/web/23440.shtml

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

相关文章

Java开发注意事项

注意:测试类中使用Autowired注解注入Bean,不要使用RequiredArgsConstructor注解注入Bean 正确示范: import org.springframework.boot.test.context.SpringBootTest; import org.springframework.beans.factory.annotation.Autowired;SpringBootTest c…

Ffmpeg安装和简单使用

Ffmpeg安装 下载并解压 进入官网 (https://ffmpeg.org/download.html),选择 Window 然后再打开的页面中下滑找到 release builds,点击 zip 文件下载 环境变量配置 下载好之后解压,找到 bin 文件夹,里面有3个 .exe 文件 然后复制…

中国互联网第一人的故事__许榕生的不平凡的经历

中国互联网第一人的故事__许榕生的不平凡的经历 目录 零 高考之际谈高考成功者 一 幸运的高考考生 二 抓住时机考研上岸 三 当年连接互联网的经过 四 互联网进入中国的缘由 五 互联网一诞生就显神威 六 互联网强国之路,我们在路上 零…

优思学院|六西格玛黑带官方的报考条件是什么?

经常有人私信问我六西格玛黑带证书要如何取得,要学历证明吗?要带项目吗?要注册吗? 首先,直接一点说,和任何学科一样,取得六西格玛证书的方法主要是通过上课学习和考试。然而,关于六…

fastapi实例

quick start 安装 pip install fastapi# ASGI服务器,生成环境可以使用uvicorn pip install uvicorn代码 from fastapi import FastAPI import uvicorn# 创建一个app实例 app FastAPI()# 编写一个路径操作装饰器 app.get("/") # 编写一个路径操作函数 …

【ARM Cache 及 MMU 系列文章 6.2 -- ARMv8/v9 Cache 内部数据读取方法详细介绍】

请阅读【ARM Cache 及 MMU/MPU 系列文章专栏导读】 及【嵌入式开发学习必备专栏】 文章目录 Direct access to internal memoryL1 cache encodingsL1 Cache Data 寄存器Cache 数据读取代码实现Direct access to internal memory 在ARMv8架构中,缓存(Cache)是用来加速数据访…

Apple开发者macOS描述文件创建

1.选择Profiles然后点击加号创建 2.选择类型为macOS App Development然后点击继续 3.选择描述类型与App ID 然后点击继续 4.选择证书然后点击继续 5.选择设备,然后点击继续 6.输入描述文件后,点击生成 生成成功,点击下载描述文件 下载完成会自动打开描述文件

评判基金的重要指标(一):最大回撤率

评判基金的重要指标(一):最大回撤率 财富奇迹创造者2023-06-14 08:00山西 问:如果要投资一只基金,如何衡量自己可能面对的风险呢? 答:衡量一个策略的风险控制能力,“最大回撤”是…

Facechain系列: 通过代码进行推理

进行推理时,需要编辑run_inference.py中的代码。为了避免人物肖像的版权问题,文章中使用的图片不是由FaceChain实际生成的图片,特此说明。 1. 以下代码适用于Linux系统,如果希望在Windows系统中运行, folder_path f…

lm studio 0.2.24国内下载模型

1.修改C:\Users\Admin\AppData\Local\LM-Studio\app-0.2.24\resources\app\.webpack\main中的3个js文件: index.js llmworker.js worker.js 中替换huggingface.co为hf-mirror.com。这样就能实现搜索模型文件 2.点击模型,选择下载,出现下载…

C语言小例程6/100

题目:输入三个整数x,y,z,请把这三个数由小到大输出。 程序分析:我们想办法把最小的数放到x上,先将x与y进行比较,如果x>y则将x与y的值进行交换,然后再用x与z进行比较,如果x>z则将x与z的值…

docker安装和使用

1. docker-ce Docker Community Edition (CE): 功能: 这是 Docker 的主要组件,用于创建、管理和运行容器。它包括 Docker 守护进程 (dockerd),该守护进程负责处理容器的生命周期,包括创建、启动、停止和删除容器。用途: 允许用户在其系统上…

T-Rex2: Towards Generic Object Detection via Text-Visual Prompt Synergy论文解读

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、引言二、文献综述1. Text-prompted Object Detection2. Visual-prompted Object Detection3. Interactive Object Detection 三、模型方法1. Visual-Text P…

Docker自定义镜像实现(SpringBoot程序为例)

✅作者简介:大家好,我是 Meteors., 向往着更加简洁高效的代码写法与编程方式,持续分享Java技术内容。🍎个人主页:Meteors.的博客💞当前专栏:知识备份✨特色专栏:知识分享&#x1f96…

Java核心: 为图片生成水印

今天干了一件特别不务正业的事,做了一个小程序用来给图片添加水印。事情的起因是需要将自己的身份证照片分享给别人,手边并没有一个趁手的工具来生成图片水印。很多APP提供了水印的功能,但会把我的图片上传到他们的服务器,身份证太…

AdSet通过审核并入驻全国SDK管理服务平台

SDK、API、H5是三种常见的APP广告接入方式,目前市面上使用最广泛的还是SDK对接,通过使用广告SDK,App开发者可以在App中展示广告商投放的广告,进而根据用户的点击赚取收益。具备一定规模流量、想快速获得收益的APP开发者都会考虑接…

使用#sortablejs插件对表格中拖拽行排序#Vue3#后端接口数据

使用#sortablejs对表格中拖拽行排序#Vue3#后端接口数据 *效果&#xff1a; 拖动表格行排序 首先安装插件sortable npm install sortablejs --save代码&#xff1a; <template><!-- sortable.js 进行表格排序 --><!-- 演示地址 --><div class"dem…

618值得推荐的洗地机有哪些?附上最全洗地机选购攻略

洗地机的出现&#xff0c;让家庭清洁变得越来越高效&#xff0c;它省时省力的洗地方式&#xff0c;自带水箱和除菌模式&#xff0c;还能减轻我们家庭清洁的负担&#xff0c;但由于目前市面上家用洗地机品牌和种类众多&#xff0c;让大家挑选起来比较困难。那么家用洗地机哪个品…

Go微服务: 关于分布式系统中的常见问题,分布式事务,以及常用解决方案

概述 在当今的互联网时代&#xff0c;分布式系统已成为支撑大规模服务、高并发和高性能应用的基石它们通过网络连接多台计算机&#xff0c;协同工作&#xff0c;共同完成任务&#xff0c;但这也引入了诸如数据一致性、网络延迟、容错性等挑战解决这些问题的关键在于设计和实施…

String,StringBuffer,StringBuilder的区别?

String是不可变的&#xff0c;StringBuffer和StringBuilder是可变的。StringBuffer是线程安全的&#xff0c;StringBuilder是非线程安全的。 String的 是如何实现的 使用拼接字符串&#xff0c;其实只是Java提供的一个语法糖。 其实String的 底层是new 了一个StringBuilde…