子查询:放心使用子查询吧!
很多开发同学喜欢用子查询,因为它逻辑清晰、更符合人类思维。但老版本 MySQL 对子查询优化不好,所以很多人不敢用。
好消息:MySQL 8.0 对子查询的优化已经非常完善,可以放心使用!
一、子查询 vs JOIN
找出1993年没有下过订单的客户数量:
写法1 - 子查询(推荐):
sql
SELECT COUNT(c_custkey) cnt
FROM customer
WHERE c_custkey NOT IN (
SELECT o_custkey FROM orders
WHERE o_orderdate >= '1993-01-01' AND o_orderdate < '1994-01-01'
);写法2 - LEFT JOIN:
sql
SELECT COUNT(c_custkey) cnt
FROM customer c
LEFT JOIN orders o ON c.c_custkey = o.o_custkey
AND o_orderdate >= '1993-01-01' AND o_orderdate < '1994-01-01'
WHERE o.o_custkey IS NULL;结论:MySQL 8.0 会自动优化,两种写法性能一样。选你喜欢的即可!
二、IN vs EXISTS,哪个快?
性能相同! 看执行计划就行,不用纠结写法。
sql
-- NOT IN
WHERE c_custkey NOT IN (SELECT o_custkey FROM orders WHERE ...)
-- NOT EXISTS
WHERE NOT EXISTS (SELECT 1 FROM orders WHERE c_custkey = o_custkey AND ...)MySQL 8.0 优化器会生成相同的执行计划,性能无差异。
三、警惕 DEPENDENT SUBQUERY
问题:子查询依赖外部表,会导致子查询执行多次,性能极差。
示例:
sql
SELECT *
FROM orders
WHERE (o_clerk, o_orderdate) IN (
SELECT o_clerk, MAX(o_orderdate)
FROM orders GROUP BY o_clerk
);执行计划:
- 表扫描 5587618 次(每次都要算一次)
- 非常慢!
优化方案 - 改写为派生表:
sql
SELECT o1.*
FROM orders o1
JOIN (
SELECT o_clerk, MAX(o_orderdate) as max_date
FROM orders GROUP BY o_clerk
) o2 ON o1.o_clerk = o2.o_clerk AND o1.o_orderdate = o2.max_date;效果:只扫描 2 次,性能提升 N 倍!
四、使用建议
| 场景 | 是否可以使用 | 注意事项 |
|---|---|---|
| MySQL 8.0 | ✅ 可以 | 直接用,优化器会处理 |
| 老版本 MySQL | ⚠️ 注意 | 检查执行计划,看是否有 DEPENDENT SUBQUERY |
| 依赖子查询 | ❌ 需优化 | 改写为派生表 |
核心结论:
- MySQL 8.0 可以放心写子查询
- IN 和 EXISTS 性能一样
- 看到 DEPENDENT SUBQUERY 一定要优化