Skip to content

子查询:放心使用子查询吧!

很多开发同学喜欢用子查询,因为它逻辑清晰、更符合人类思维。但老版本 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
依赖子查询❌ 需优化改写为派生表

核心结论

  1. MySQL 8.0 可以放心写子查询
  2. IN 和 EXISTS 性能一样
  3. 看到 DEPENDENT SUBQUERY 一定要优化