面试官:Mysql中EXISTS与IN的使用有哪些差异

在数据库查询优化中,查询效率直接关系到应用程序性能。其中,IN和EXISTS是两种常见的子查询操作符,广泛应用于SQL查询语句,但它们在执行效率上有所不同。

本文深入探讨IN和EXISTS的工作原理,以及在何种情境下选择更为合适。通过对这两种操作符的详细分析,揭示它们在实际应用中的优缺点,一起了解如何在数据库查询中灵活运用IN和EXISTS,以优化查询语句的执行。

IN与EXISTS基本概念与用法

IN子查询

在MySQL中,当使用IN子查询时,主查询(外表)中的每一行都会与子查询(内表)的结果集进行比较。先执行子查询生成一个临时表,然后主查询取出对应的字段值,系统会遍历子查询结果集,检查这个字段值是否存在于子查询结果集中。如果存在,则该行满足条件,会被加入到最终的查询结果中。例如:

SELECT * FROM t_order where customer_no in (SELECT customer_no FROM t_customer WHERE country = 'US');

在这个例子中,对于t_order表中的每一行,MySQL会查看t_customer表中是否存在与其customer_no相匹配的记录。如果t_customer表中有任何行的customer_not_order表中当前行的customer_no相同,那么这一行就会被包含在最终查询结果中。

IN子查询的效率通常在子查询结果集较小的情况下较高,因为它需要处理并可能缓存整个子查询结果。

EXISTS子查询

EXISTS子查询则是用于判断关联性,它并不关心子查询返回的具体数据值,而只关注是否存在匹配的行。对于主查询表中的每一行,执行内部的EXISTS子查询。当EXISTS子查询找到一行或多行符合WHERE条件的记录时,立即返回真(TRUE)。这个TRUE值会导致外层查询的那一行被纳入最终结果中,因为WHERE EXISTS条件为真。一旦EXISTS子查询找到匹配项,它就不需要继续查找剩余的记录了,即实现了所谓的“短路”或“早期终结”。例如:

SELECT * FROM t_order torder WHERE EXISTS(SELECT 1 FROM t_customer tcustomer WHERE tcustomer.customer_no = torder.customer_no AND tcustomer.country = 'US');

在这个例子中,只要t_customer表中存在至少一条记录,其customer_not_order表中的当前行customer_no相符,MySQL就认为EXISTS条件为真,并将当前的t_order表行作为结果返回。无论t_customer表有多少其他相关记录,都不再影响此条目是否被选中。
EXISTS在子查询表大但只需验证是否存在对应关系时更高效,它支持“短路”机制,一旦找到匹配项就结束子查询,不必遍历完整个子查询表。

结论

MySQL中的IN语句是把外表和内表作HASH连接,而EXISTS语句是对外表作LOOP循环,每次LOOP循环再对内表进行查询,单纯的理解EXISTSIN语句的效率要高的说法其实是不准确的,要区分情景:

  • 如果查询的两不表大小相当,那么用EXISTSIN差别不大。
  • 如果两个表中一个较小,一个是大表,则子查询表大的用EXISTS,子查询表小的用 IN

验证

下面我们来通过实际案例去验证数据量和索引对INEXISTS子查询性能的影响。
我们创建两张表:

-- t_order
DROP TABLE IF EXISTS `t_order`;  
CREATE TABLE `t_order`(  
 id                 bigint UNSIGNED AUTO_INCREMENT COMMENT '自增主键'  
        PRIMARY KEY,  
 `order_no` varchar(16) NOT NULL DEFAULT '' COMMENT '订单编号',  
 `customer_no`      varchar(16) NOT NULL DEFAULT '' COMMENT '客户编号',  
`create_time`      datetime        NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'  
) ENGINE = InnoDB  
  AUTO_INCREMENT = 1  
  DEFAULT CHARSET = utf8mb4 COMMENT ='测试订单表';  

-- t_customer
DROP TABLE IF EXISTS `t_customer`;  
CREATE TABLE `t_customer`(  
     id                 bigint UNSIGNED AUTO_INCREMENT COMMENT '自增主键'  
        PRIMARY KEY,  
     `customer_no`      varchar(16) NOT NULL DEFAULT '' COMMENT '客户编号',  
    `create_time`      datetime        NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'  
)ENGINE = InnoDB  
  AUTO_INCREMENT = 1  
  DEFAULT CHARSET = utf8mb4 COMMENT ='测试订单客户表';

我们通过Python脚本往t_order中插入100万条数据,t_customer中插入1万条数据。

案例sql:

SELECT * FROM t_order where customer_no in (SELECT customer_no FROM t_customer WHERE country = 'US');

SELECT * FROM t_order torder WHERE EXISTS(SELECT 1 FROM t_customer tcustomer WHERE tcustomer.customer_no = torder.customer_no AND tcustomer.country = 'US');

案例执行建立在没有加索引情况下进行。

IN小表,EXISTS小表

我们在执行上面两条sql时会发现IN查询的速度远远高于EXISTS

SELECT * FROM t_order where customer_no in (SELECT customer_no FROM t_customer WHERE country = 'US');

SELECT * FROM t_order torder WHERE EXISTS(SELECT 1 FROM t_customer tcustomer WHERE tcustomer.customer_no = torder.customer_no AND tcustomer.country = 'US');

我们先看两个sql的执行计划:
image.png
本案例中IN查询的SQL执行了近5秒。
image.png
本案例中EXISTS查询的SQL执行了超过5分钟。

从上述执行计划中,我们可以看到IN查询和EXISTS查询在没有索引的情况下都进行了全表扫描:

  • IN查询:

    • 主查询对t_order表进行了全表扫描(ALL),由于没有索引,MySQL需要遍历1005915行数据。
    • 子查询对t_customer表也进行了全表扫描(MATERIALIZED),查找国家为’US’的客户编号。该表大小较小,有1000行数据。
  • EXISTS查询:

    • 主查询同样对t_order表进行了全表扫描(ALL),同理,无索引导致效率较低。
    • 子查询对t_customer表进行了全表扫描(DEPENDENT SUBQUERY),并且根据WHERE条件过滤出与主查询关联的数据。

虽然两者都未使用索引,但根据执行计划中的rows值,IN查询的子查询涉及的数据量要远小于主查询涉及的数据量。具体来说,在IN查询中,子查询只需要处理1000行数据,并将结果用于筛选主查询中的1005915行数据。而在EXISTS查询中,子查询虽然只返回1.00(几乎为1)个匹配记录,但它需要针对每一行主查询的结果进行检查,总共要处理1005915次。

因此,在这种情况下,IN查询的效率高于EXISTS查询的原因主要是子查询数据集大小的不同以及子查询对主查询的影响程度。尽管两个查询都没有利用到索引优化,但在实际执行时,IN查询所需的计算量相对较小,故其性能优于EXISTS查询。

IN大表,EXISTS大表

我们再次变更一下sql,让子查询是大表,观察一下他们的执行情况。即sql:

SELECT * FROM t_customer WHERE customer_no IN (SELECT customer_no FROM t_order);

SELECT * FROM t_customer tcustomer WHERE EXISTS(SELECT 1 FROM  t_order torder WHERE tcustomer.customer_no = torder.customer_no);

我们再次查看sql的执行计划:
image.png
执行IN查询语句时花费2秒。

image.png
执行EXISTS查询花费0.25秒。

由此可以看出EXISTS查询的效率远高于IN查询,我们结合执行计划进行分析:

  • IN查询:
    • 主查询(t_customer)由于没有索引,MySQL需要对整个表进行全表扫描,涉及行数为1000行。
    • 子查询(t_order)同样进行了全表扫描,涉及行数为1005915行。虽然子查询的结果通过自动生成的临时键与主查询关联,并且对于每一个主查询中的customer_no,子查询都能很快找到对应的记录(rows值为1),但由于子查询的数据量巨大,所以整体查询效率不高。
  • EXISTS查询:
    • 主查询(t_customer)仍然进行了全表扫描,涉及行数为1000行。
    • 子查询(t_order)也是全表扫描,但关键在于它是“DEPENDENT SUBQUERY”,这意味着它会依赖于外部查询(即主查询)的每一行结果来决定是否执行。尽管子查询需要处理1005915行数据,但由于其是根据主查询的每一条customer_no逐个检查是否存在匹配项,因此当遇到第一条不满足条件的customer_no时,就可以立即停止对子查询中剩余行的处理。这导致了在实际执行过程中,可能只需要检查一部分t_order表的数据即可完成所有主查询记录的验证,从而提高了查询效率。

在这个案例中,因为主查询表(t_customer)较小,而子查询表(t_order)较大,EXISTS查询能够在较早阶段停止不必要的计算,使得整体查询效率优于IN查询。

数据量以及索引对IN与`EXISTS性能的影响

在MySQL中,INEXISTS子查询的性能很大程度上取决于内外表的数据量以及相关的索引设置。

数据量的影响
  • 对于IN子查询:当内表(子查询结果集)较小且数据能够被有效索引时,IN通常表现良好。如果内表很大,即使有索引,由于需要生成并存储完整的子查询结果集以供主查询进行比对,因此随着内表记录数的增长,性能会逐渐下降。

  • 对于EXISTS子查询:当外层主查询表较大,而内表虽大但匹配条件的行数较少时,EXISTS的优势更加明显。因为它仅需找到一个匹配项就可以立即结束内部循环,返回真值,无需遍历整个内表。当内表数据量巨大但能快速定位到满足条件的少数行时,EXISTS相比IN更高效。

索引的影响:
  • 对于IN子查询:如果IN子查询中的字段具有有效的索引,可以减少内表的全表扫描,转而通过索引查找,显著提高查询效率。尤其是覆盖索引(索引包含了查询所需的所有列),可以直接从索引中获取信息,避免回表操作。

  • 对于EXISTS子查询:对于EXISTS子查询,同样要求相关联的字段上有合适的索引。例如,在上面的例子中,若t_customercustomer_no字段有索引,那么在执行WHERE tcustomer.customer_no = torder.customer_no时,可以通过索引快速定位匹配记录,从而加速子查询的执行过程。

在决定使用IN还是EXISTS时,首先应考虑的是内外表的数据规模以及关联字段上的索引情况。若内表较小或子查询结果集易于通过索引优化,IN可能是更好的选择。若关注是否存在关联关系且内表虽大但能满足条件的行数有限,同时外层主查询表可能更大,则EXISTS可能提供更高的查询性能。

当然最佳实践是结合实际业务需求、数据分布特点以及数据库统计信息,通过分析SQL执行计划来确定最合适的查询策略,并根据实际情况调整表结构和索引设计。

本文已收录于我的个人博客:码农Academy的博客,专注分享Java技术干货,包括Java基础、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中间件、架构设计、面试题、程序员攻略等。

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

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

相关文章

QGIS使用地理配准将3857坐标系转成上海城建坐标

控制点格式 如 mapX mapY sourceX sourceY enable dX dY residual -58653 70641 13452659.39 3746386.025 1 0 0 0 -58653 65641 13452693.09 3740477.283 1 0 0 0 ......保存为.points格式 图层预处理 图层投影为3857坐标系 地理配准 1. 打开图层-地理配准 工具 2. 导入…

ATAC-seq发篇测序文章就结束了吗?看如何利用ATAC-seq数据为后续关键基因的转录调控研究提供重要依据

染色质可及性(Chromatin Accessibility)是染色质的一种特性,为转录因子结合靶基因提供了空间。转座酶可及染色质测序分析(ATAC-seq)是常见的研究染色质可及性的方法,ATAC-seq联合RNA-seq是一种新的研究思路…

day10 Javaweb

第一章 WEB概述 1.1 JAVAWEB简介 用Java技术来解决相关web互联网领域的技术栈.使用JAVAEE技术体系开发企业级互联网项目. 项目规模和架构模式与JAVASE阶段有着很大的差别. 在互联网项目下,首先需要明白客户端和服务器的概念 客户端 :与用户进行交互,用于接收用户的…

解决ubuntu 在VMware Workstation Pro下显示不完整的问题

步骤一 ctrlaltT 打开终端,输入: xrandr -s 1920x1080如果报错就输入xrandr,从里面选择适合的分辨率 注意是字母x不是乘号 步骤二 按win键,在搜索框搜索resolution更改显示器分辨率: 选择与电脑相同的分辨率,点击…

(二十一)Flask之上下文管理第二篇(细细扣一遍源码)

每篇前言: 🏆🏆作者介绍:【孤寒者】—CSDN全栈领域优质创作者、HDZ核心组成员、华为云享专家Python全栈领域博主、CSDN原力计划作者 🔥🔥本文已收录于Flask框架从入门到实战专栏:《Flask框架从入…

【MISRA C 2012】Rule 5.6 类型定义名称应该是唯一的标识符

1. 规则1.1 原文1.2 分类 2. 关键描述3. Example4. 代码实例 1. 规则 1.1 原文 1.2 分类 规则5.6:类型定义名称应该是唯一的标识符 Required要求类规范。 2. 关键描述 typedef名称在所有名称空间和翻译单元中应该是唯一的。只有当类型定义在头文件中并且该头文件…

HiveSQL题——窗口函数(lag/lead)

目录 一、窗口函数的知识点 1.1 窗户函数的定义 1.2 窗户函数的语法 1.3 前后函数:lag/lead 二、实际案例 2.1 股票的波峰波谷 0 问题描述 1 数据准备 2 数据分析 3 小结 2.2 前后列转换(面试题) 0 问题描述 1 数据准备 2 数据分析 3 小结…

【C语言】linux内核ipoib模块 - ipoib_netdev_ops_pf结构

一、ipoib_netdev_ops_pf结构 static const struct net_device_ops ipoib_netdev_ops_pf {.ndo_init ipoib_ndo_init,.ndo_uninit ipoib_ndo_uninit,.ndo_open ipoib_open,.ndo_stop ipoib_stop,.ndo_change_mtu ipoib_change_mtu,.ndo_…

将word中插入的zotero文献转为latex引用样式

在word中,可以使用zotero插件插入和管理参考文献。 例如: 若需要将这段内容移植到latex,就需要将对应的引用编号用\cite{}替换。手动替换非常麻烦且容易出错。要实现自动转换,可在zotero中定制一种新的引文格式,自动将…

【大数据】Flink 架构(六):保存点 Savepoint

《Flink 架构》系列(已完结),共包含以下 6 篇文章: Flink 架构(一):系统架构Flink 架构(二):数据传输Flink 架构(三):事件…

云上高可用系统-韧性设计模式

一、走近韧性设计模式 (一)基本概念 韧性设计模式是一系列在软件工程中用于提高系统韧性的设计原则、策略、实践和模式。韧性(Resilience)在这里指的是系统对于各种故障、异常和压力的抵抗能力,以及在遭受这些挑战后…

重生奇迹MU弓箭手技能搭配

重生奇迹mu弓箭手技能怎么选择和搭配呢?重生奇迹mu弓箭手是一个远程射手定位的英雄,下面为大家带来重生奇迹mu弓箭手技能选择攻略。 首先,从人物属性上看,弓箭手属于敏捷型高攻单位。在重生奇迹mu中敏捷属性带来的收益在于高质量…

Mac下查看、配置和使用环境变量

Mac下查看、配置和使用环境变量 一:Mac怎么查看环境变量命令 printenv一:这个命令会一次性列出所有环境变量的键值对,输出格式为: VAR1value1 VAR2value2 ...二: 也可以通过给这个命令加上环境变量名参数&#xff0…

循环购模式——解决用户复购难 用户粘性差!

随着电商市场的竞争加剧,消费者需求日益多样化,电商企业需要不断创新以满足市场需求。循环购模式作为一种新兴的消费模式,正逐渐受到消费者的青睐,成为电商行业的新热点。本文将深入剖析循环购模式的魅力,探索其在电商…

天猫超市卡回收平台

京回收等卡商能回收天猫超市卡 天猫商城当中的天猫超市,实际也是类似于京东自营店有关销售货物模式,对于天猫超市里面的东西,均是由天猫进行管理,并从商品的采购,就是一手进行操作。相关的物品,以相关商城会…

JVM篇----第十四篇

系列文章目录 文章目录 系列文章目录前言一、JVM 类加载机制二、类加载器三、双亲委派前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 一、JVM 类加载机制 JVM 类…

【每日一题】5.LeetCode——环形链表

📚博客主页:爱敲代码的小杨. ✨专栏:《Java SE语法》 ❤️感谢大家点赞👍🏻收藏⭐评论✍🏻,您的三连就是我持续更新的动力❤️ 🙏小杨水平有限,欢迎各位大佬指点&…

JS基础 - 遍历对象方法(6种)

初始值: var obj {a: 1,b: 2,c: 3,d: 4,e: 5,}; 第一种:for in for (let key in obj) {console.log(key ":" obj[key]);} 第二种:Object.keys 获取key Object.keys(obj).forEach((key) > {console.log(key ":" …

RT-DETR改进有效系列目录 | 包含卷积、主干、RepC3、注意力机制、Neck上百种创新机制

💡 RT-DETR改进有效系列目录 💡 前言 Hello,各位读者们好 Hello,各位读者,距离第一天发RT-DETR的博客已经过去了接近两个月,这段时间里我深入的研究了一下RT-DETR在ultralytics仓库的使用,旨在为大家解决为什么用v8的仓库训练的时候模型不收敛,精度差的离谱的问题,…

swift - reduce简介

reduce 减少&#xff0c;降低&#xff1b;&#xff08;烹调中&#xff09;使变浓稠&#xff0c;收汁&#xff1b;<美>节食减肥&#xff1b;使沦为&#xff0c;使陷入&#xff08;不好的境地&#xff09;&#xff1b;迫使&#xff0c;使不得不&#xff08;做&#xff09;&…