JOIN、IN、EXISTS谁最快?实测三种写法性能差异与执行计划深度剖析

本文涉及的产品
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS AI 助手,专业版
PolarDB Agent Express,2核4GB
简介: 本文用MySQL 8.0实测拆解`IN`/`EXISTS`/`JOIN`子查询性能:从执行计划、半连接优化、临时表开销等底层原理出发,结合10万+100万数据实测(`EXISTS`最快95ms),给出三条选型铁律——告别盲从“最佳实践”,只选最适配业务与数据的写法!

大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!

上周那篇关于子查询优化的文章发出来后,评论区炸了。有人说“用JOIN是错的”,有人说“EXISTS才是正解”。今天我不站队,直接用实测数据说话,从执行计划层面彻底拆解这三种写法。

1 问题背景:开发中的常见困惑

在日常开发里,INEXISTSJOIN的争论我听过无数遍:

  • “数据量不大的时候用IN最直观,为什么网上都说要改成EXISTS?”
  • “我的IN子查询明明有索引,为什么EXPLAIN还是显示全表扫描?”
  • NOT INNOT EXISTS结果一样吗?性能差多少?”
  • “子查询改写为JOIN后为什么结果里多了重复行?”

这些困惑的根源在于:不同写法在数据库优化器中的处理逻辑截然不同,而且优化器的选择还受到MySQL版本、数据分布、索引设计和统计信息的影响。今天我们就从底层执行路径开始讲起。

2 核心概念:理解IN、EXISTS、JOIN的执行逻辑

要判断谁更快,必须先理解优化器是如何执行这三种写法的。

2.1 IN:物化子查询或半连接

SELECT * FROM users WHERE user_id IN (SELECT user_id FROM orders);

在MySQL 5.5及更早版本中,IN子查询的执行方式是​物化​:先完整执行子查询,将结果集存储在内部临时表中,然后外层查询再与该临时表进行匹配。这种方式在子查询结果集较大时,临时表的构建和磁盘I/O会成为主要瓶颈。

从MySQL 5.6开始,优化器引入了半连接优化。当满足一定条件(子查询无GROUP BY、无聚合、非相关子查询等)时,优化器会将IN子查询转换为类似JOIN的半连接执行路径,性能得到显著提升。

2.2 EXISTS:半连接与匹配即停

SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.user_id);

EXISTS子查询是​相关子查询​:它会将外层查询的每一行代入子查询,一旦在内层表中找到匹配记录,就会立即停止扫描并返回结果。这使得EXISTS在处理存在性检查时非常高效。但它的开销高度依赖于外层数据量:如果外层表很大,内层索引够快,EXISTS依然高效;如果外层表巨大且内层索引不佳,逐行代入的成本也会很高。

2.3 JOIN:笛卡尔积与去重代价

SELECT DISTINCT u.* FROM users u JOIN orders o ON u.user_id = o.user_id;

JOIN的执行路径是先将两张表按关联条件进行匹配,然后通过索引快速筛选出符合条件的行。如果users表中一个用户有多个订单,JOIN会产生重复行,因此必须使用DISTINCT去重。而DISTINCT在MySQL中通常需要创建临时表进行去重操作,当数据量较大时临时表可能溢出到磁盘,带来额外的性能损耗。

3 实测对比:同一场景下的三种写法

3.1 测试环境

  • 数据库:MySQL 8.0.33
  • users表:10万行,user_id为主键
  • orders表:100万行,user_id有二级索引
  • 目标:查询所有下过单的用户信息

3.2 三种写法的执行时间

写法 平均耗时(3次运行) 执行计划特点
IN 128ms 半连接,使用物化或索引
EXISTS 95ms 半连接,匹配即停
JOIN + DISTINCT 236ms DISTINCT产生临时表,大表时可能写磁盘

3.3 结果分析

在本测试环境中,EXISTS性能最优,IN次之,JOIN最慢。JOINDISTINCT临时表开销和重复行匹配是其主要性能瓶颈。但需要注意的是,这一结论依赖于特定数据分布和索引设计——如果子查询结果集极小,IN可能更快;如果需要同时返回两表的字段,JOIN则是唯一合理的选择。

4 执行计划深度解码:为什么会有这样的结果?

我们通过EXPLAINEXPLAIN FORMAT=TREE来观察优化器的决策过程。

4.1 EXPLAIN输出对比

-- EXPLAIN for IN
EXPLAIN SELECT * FROM users WHERE user_id IN (SELECT user_id FROM orders);

输出中关键信息:select_typePRIMARYtype=ALL(外层全表扫描),SUBQUERYtype=index(子查询索引扫描)。这意味着MySQL先执行子查询拿到所有user_id,再逐行匹配外层。

4.2 EXPLAIN关键列解读

列名 作用 本次对比中的表现
select_type 标识子查询类型 INEXISTS均为半连接优化
type 访问类型,ALL=全表扫描,ref=索引查找 IN的驱动表为ALLEXISTS的驱动表为ref
Extra 附加信息 JOIN版本可能显示Using temporary
filtered 过滤后剩余比例 影响回表代价估算

5 选型决策指南:三条铁律

基于以上分析,我总结出三条实用的选择策略:

  1. 存在性检查(业务逻辑为“是否有订单”)​:优先使用EXISTS。其“匹配即停”的机制和半连接优化,使其在大多数场景下性能最优且语义最清晰。
  2. 子查询结果集非常小(如几十行)且不重复​:IN的可读性最好,由于结果集极小,物化临时表的代价几乎可以忽略。
  3. 需要同时返回A表和B表的字段​:必须使用JOIN。但需通过业务逻辑判断是否需要DISTINCT去重,尽可能避免不必要的去重操作。

6 总结

没有绝对的“最快写法”,只有基于场景和数据特征的“最合适写法”。

  • EXISTS适合存在性检查,匹配即停,通常稳定性最好。
  • IN在子查询结果集极小时可读性最佳,性能也可接受。
  • JOIN在需要两表字段时不可替代,但需关注去重成本。

以后遇到这类问题,先问自己三个问题:业务是要判断存在还是取数据?子查询结果集大不大?能不能接受去重临时表?想清楚再写,比抄网上的“最佳实践”靠谱得多。

小耶在手,SQL不愁。

还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽……我们下次见~

相关文章
|
2月前
|
SQL 关系型数据库 MySQL
EXPLAIN 执行计划:一眼看穿你的SQL慢在哪
数据库小学妹带你轻松掌握SQL性能诊断!通过EXPLAIN查看执行计划,精准识别索引失效、全表扫描(ALL)、key为NULL等瓶颈。聚焦type、key、rows等6个关键字段,结合实战案例与避坑指南(如函数滥用、最左前缀破坏),让优化有的放矢。学完即用,告别盲目调优!
|
24天前
|
SQL 关系型数据库 MySQL
MySQL慢查询诊断实战:从10秒到0.1秒,我的5步排障法
数据库小学妹分享慢查询优化实战:从10秒降至0.08秒!详解「发现→收集→分析→优化→验证」5步排障法,覆盖慢日志配置、EXPLAIN进阶、索引失效场景、JOIN与分页优化等核心技巧,附真实案例与速查表。
|
1天前
|
SQL 安全 Java
SQL注入防御指南:从漏洞原理到实战防护,我的安全避坑血泪史
数据库小学妹带你秒懂SQL注入防护!📌核心关键词:SQL注入、参数化查询、预编译、WAF。用餐厅点餐类比攻击原理,详解布尔盲注、时间延迟、联合查询三种手法;手把手演示Python/Java/PHP/C#安全写法;构建“参数化(必选)+输入校验(辅助)+最小权限(兜底)”三层防御体系,并推荐WAF、ORM与扫描工具。安全无小事,从杜绝字符串拼接开始!
|
2月前
|
存储 数据采集 分布式计算
数据仓库是什么?数据仓库和大数据平台、数据湖、数据中台、湖仓一体有什么区别?
本文厘清数据仓库、大数据平台、数据湖、数据中台、湖仓一体五大核心概念的本质区别与适用场景,破除术语混淆误区。从架构定位、数据类型、建模方式、技术演进到典型优劣,逐一剖析,助你精准选型、科学设计、自信汇报。
|
24天前
|
弹性计算 人工智能 测试技术
2026年阿里云便宜云服务器推荐与选购指南
2026年阿里云推出史上最强优惠:打破新老用户壁垒,实现“新老同价、续费同价”。99元/年e实例、199元/年u1实例长期稳定;新用户可抢38元/年轻量服务器;企业享百万迁云补贴与GPU 4折。省钱避坑指南,助你轻松上云!
371 4
|
1天前
|
SQL 监控 关系型数据库
数据库三大日志深度解析:Redo Log、Binlog、Undo Log 如何守护你的数据
本文由“数据库小学妹”带你厘清MySQL三大核心日志:Redo Log(引擎层物理日志,保障crash-safe)、Undo Log(支撑回滚与MVCC)和Binlog(Server层逻辑日志,用于复制与恢复),详解WAL机制与两阶段提交原理,助你真正理解事务安全底层逻辑。
|
30天前
|
SQL 缓存 关系型数据库
主从延迟的5大“元凶”+3个排查命令,别再让从库拖后腿
数据库小学妹详解MySQL主从延迟:5大元凶(硬件弱、写压大、慢查询、网络差、大事务)+3条核心排查命令(SHOW SLAVE STATUS等),助你快速定位、精准优化,避坑生产故障!
|
2月前
|
SQL 数据库 数据库管理
写完SQL先别跑,这两步能救你一晚
我是小耶,专注踩坑与填坑,今天分享SQL性能关键:数据库执行顺序(FROM→WHERE→…)与人脑思维的错位——切忌先JOIN后过滤!用实例对比,教你“过滤前置”提速技巧。养成自查习惯,SQL轻松快一倍!
|
28天前
|
SQL 关系型数据库 MySQL
批量操作性能飙升:从30秒到1秒的三种实战方法
业务系统中经常需要批量导入或更新大量数据(如Excel上传、定时同步)。许多开发人员采用循环单条执行的方式,导致1万条数据耗时30秒以上,严重影响用户体验。本文从数据库IO、事务开销、锁竞争三个角度分析单条操作的性能瓶颈,并给出三种优化方案:批量INSERT、LOAD DATA文件导入、批量UPDATE用临时表。每种方案均附实测数据对比与适用场景说明,帮助读者在1万\~100万行级别批量操作中选择最优策略。
|
1月前
|
SQL 运维 关系型数据库
DBA必备技能:MySQL误删恢复完全指南(全量备份+binlog回放)
本文详解误删数据(如`DELETE FROM orders`)后的紧急恢复三步法:查Binlog→临时库回放→差异导回,并附4条血泪预防措施。不讲段子,只教能救命的操作!