别再滥用IN子查询了!用JOIN改写,从8秒到0.4秒(附优化步骤)

本文涉及的产品
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS DuckDB + QuickBI 企业套餐,8核32GB + QuickBI 专业版
简介: 本文揭秘SQL子查询性能陷阱:IN慢因临时表+全量扫描;推荐JOIN改写——利用索引、避免磁盘IO。实测500万订单下,JOIN比IN快20倍!附三步改写法与NULL避坑指南。

我是小耶,干运营半路出家的野生DBA——写功课只是为了我踩过的坑,你们别再踩了!

刚学SQL那会儿,遇到“在A表里查B表也有的数据”,我总喜欢写 IN 子查询,因为好理解,像英语一样:user_id IN (SELECT user_id FROM orders)。后来有一次,我写了一个这样的查询,跑了快十分钟都没出结果,这才认真去研究它为什么慢。

子查询慢的原因,可以这样理解:就像你打电话给餐厅,让服务员把所有菜名念一遍(生成一个大列表),然后你拿着这个列表一样一样去找你想吃的。如果餐厅有几百道菜,这个过程会非常慢。

在数据库里,子查询会先产生一个临时结果集,可能放在内存或磁盘里,然后外层查询逐行去匹配。如果子查询返回几百万行,临时表巨大,内存放不下就会写磁盘,IO飙升,速度自然快不起来。

怎么改?能JOIN就别子查询。

举个例子:查询“下过单的用户”中的VIP用户。

❌ 较慢的写法(子查询):

SELECT * FROM users 
WHERE vip_level = 3 
  AND user_id IN (SELECT DISTINCT user_id FROM orders);

✅ 快得多的写法(JOIN):

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

注意加了 DISTINCT,因为一个用户可能下多个订单,JOIN会产生重复,要去重。

为什么JOIN快?

  • 可以利用 orders.user_id 上的索引
  • 优化器会选择小表驱动大表(通常VIP用户数量较少)
  • 不会生成巨大的中间临时表

子查询什么时候还凑合?

  • 子查询的结果集非常小(比如只返回几十行),写起来简单,性能差别不大
  • EXISTS 在某些场景下比 IN 好,尤其是子查询大但外层能快速匹配时

特别提醒:NOT IN 要小心 NULL 值——如果子查询结果中包含 NULLNOT IN 会返回空结果,所以更推荐用 NOT EXISTS

实测数据
我拿一张500万行的订单表、50万行的用户表做了对比:

  • IN 子查询:8.3秒
  • JOIN 写法:0.4秒
    差距超过20倍。

改写三步骤

  1. 把子查询中的表放到 FROM 里,用 JOIN 连接
  2. 如果原SQL用了 DISTINCT 或担心重复,加上 DISTINCT 或用 GROUP BY
  3. 确保 JOIN 的关联字段有索引(例如 orders.user_id 要有索引)

学会用JOIN改写子查询,是SQL优化的进阶门槛。以后看到 INEXISTS,先问问自己:子查询结果集大不大?大就改JOIN。这个习惯能帮你省下很多加班时间。

小耶在手,SQL不愁。

你遇到过子查询跑崩的情况吗?评论区分享一下。

相关文章
|
1月前
|
SQL 关系型数据库 MySQL
一张5000万行的表,加索引从45秒到0.02秒——索引设计你真的会吗
本文实测5000万订单表:无索引查询45秒,加索引后仅0.02秒(提升2250倍)。详解索引原理、建索引时机、联合索引最左前缀、覆盖索引及隐式转换陷阱,干货不啰嗦!
|
1月前
|
SQL 关系型数据库 MySQL
MySQL主从复制实战:从原理到读写分离,新手避坑全指南
数据库小学妹带你轻松入门主从复制!✅基于binlog实现主库写、从库读,支撑读写分离与高可用;🛡️保障数据安全(灾备)、提升并发能力;🔧详解三种复制模式、搭建步骤、延迟优化及避坑指南。运维进阶必备!
|
1月前
|
SQL 运维 关系型数据库
DBA必备技能:MySQL误删恢复完全指南(全量备份+binlog回放)
本文详解误删数据(如`DELETE FROM orders`)后的紧急恢复三步法:查Binlog→临时库回放→差异导回,并附4条血泪预防措施。不讲段子,只教能救命的操作!
|
25天前
|
关系型数据库 MySQL 测试技术
JOIN、IN、EXISTS谁最快?实测三种写法性能差异与执行计划深度剖析
本文用MySQL 8.0实测拆解`IN`/`EXISTS`/`JOIN`子查询性能:从执行计划、半连接优化、临时表开销等底层原理出发,结合10万+100万数据实测(`EXISTS`最快95ms),给出三条选型铁律——告别盲从“最佳实践”,只选最适配业务与数据的写法!
|
25天前
|
SQL 关系型数据库 MySQL
批量操作进阶:百万行级数据导入的性能极限
本文分享百万行数据导入四大进阶技巧:分区表减少锁竞争、禁用索引加速写入、并行LOAD DATA榨干多核性能、金仓kdb_load专用工具再提速。实测100万行最快<1秒,助你从分钟级跃升秒级!
|
1月前
|
SQL 缓存 数据库
你还在用LIMIT 1000000,10?献上分页查询优化技巧
本文详解“深分页”陷阱:`LIMIT 1000000,10`为何慢?3种优化方案(游标法、子查询定位、延迟关联)实测提速数十倍,助你零成本提升SQL性能!
|
1月前
|
缓存 NoSQL 关系型数据库
告别低效COUNT(*)!数据库计数优化完全指南
本文详解`COUNT(*)`在千万级表中变慢的根源(InnoDB MVCC机制),对比`COUNT(*)`/`COUNT(1)`/`COUNT(列)`差异,并提供EXPLAIN估算、计数表、Redis缓存三大优化方案及选型建议。
|
1月前
|
SQL 关系型数据库 MySQL
MySQL隐式转换的坑:类型不匹配,索引全废——一个小符号让你慢查询翻车
这篇干货专治MySQL“隐式转换”坑:varchar字段不加引号导致索引失效、全表扫描慢如蜗牛……一个引号之差,性能差万倍!附排查方法、修复案例与避坑口诀,帮你少踩坑、多省命。
|
29天前
|
SQL 关系型数据库 MySQL
批量操作性能飙升:从30秒到1秒的三种实战方法
业务系统中经常需要批量导入或更新大量数据(如Excel上传、定时同步)。许多开发人员采用循环单条执行的方式,导致1万条数据耗时30秒以上,严重影响用户体验。本文从数据库IO、事务开销、锁竞争三个角度分析单条操作的性能瓶颈,并给出三种优化方案:批量INSERT、LOAD DATA文件导入、批量UPDATE用临时表。每种方案均附实测数据对比与适用场景说明,帮助读者在1万\~100万行级别批量操作中选择最优策略。
|
1月前
|
SQL 人工智能 数据库
模型都卷成麻花了,你还在用老办法管数据库?
本文聚焦大模型爆发下DBA新挑战:AI Agent带来的动态查询压力、向量检索成标配、NL2SQL重塑取数流程。倡导“懂数据+懂业务+懂AI”的复合能力转型。