我是小耶,干运营半路出家的野生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 值——如果子查询结果中包含 NULL,NOT IN 会返回空结果,所以更推荐用 NOT EXISTS。
实测数据
我拿一张500万行的订单表、50万行的用户表做了对比:
IN子查询:8.3秒JOIN写法:0.4秒
差距超过20倍。
改写三步骤
- 把子查询中的表放到
FROM里,用JOIN连接 - 如果原SQL用了
DISTINCT或担心重复,加上DISTINCT或用GROUP BY - 确保
JOIN的关联字段有索引(例如orders.user_id要有索引)
学会用JOIN改写子查询,是SQL优化的进阶门槛。以后看到 IN、EXISTS,先问问自己:子查询结果集大不大?大就改JOIN。这个习惯能帮你省下很多加班时间。
小耶在手,SQL不愁。
你遇到过子查询跑崩的情况吗?评论区分享一下。