在数据库开发中,子查询是非常常见的写法,但如果不了解它的执行机制,很容易写出性能很差的 SQL。一般来说,SQL 子查询在以下几种典型情况下会变慢。

一、相关子查询被反复执行
相关子查询是指子查询中引用了外部查询的列,数据库往往需要对外部查询的每一行都执行一次子查询。当外部表数据量较大时,执行次数会呈倍数增长。
-- 对 employees 每一行都执行一次子查询
SELECT e.name,
(SELECT d.dept_name
FROM departments d
WHERE d.id = e.dept_id) AS dept_name
FROM employees e;
这种写法在 employees 表有十万行时,子查询就会执行十万次。可以考虑改成 JOIN 来避免重复计算。
二、子查询返回大数据集且缺乏索引
如果子查询返回的结果集很大,并且后续操作如 IN、EXISTS 或 JOIN 没有合适索引支撑,数据库可能被迫进行全表扫描或生成临时表。
-- 子查询返回大量 id,主表 user_id 无索引时很慢
SELECT *
FROM orders
WHERE user_id IN (
SELECT id
FROM users
WHERE status = 'active'
);
为 users.status 和 orders.user_id 建立索引通常能明显提升速度。
三、子查询出现在 SELECT 列表且逻辑复杂
SELECT 中的标量子查询如果包含聚合或关联逻辑,优化器很难将其扁平化,容易变成逐行计算。
SELECT p.id,
(SELECT COUNT(*)
FROM order_items oi
WHERE oi.product_id = p.id) AS sale_count
FROM products p;
此类统计更适合用 LEFT JOIN 加 GROUP BY 一次性算出。
四、优化器无法良好展开子查询
某些嵌套多层或带 DISTINCT、GROUP BY 的子查询,优化器可能将其物化为临时表,带来额外 IO 和内存开销。可通过 EXPLAIN 查看是否出现 MATERIALIZED 字样。
| 场景 | 可能变慢原因 |
|---|---|
| 相关子查询 | 外部每行触发一次执行 |
| 大结果集子查询 | 无索引导致全表扫描 |
| SELECT 中复杂子查询 | 难以扁平化优化 |
五、改写建议
- 用 JOIN 替代相关子查询
- 为过滤列和关联列建立索引
- 用 GROUP BY 预聚合代替 SELECT 中计数
- 使用 EXPLAIN 分析执行计划
子查询本身不是性能问题,关键在于它如何被执行以及数据规模与索引情况。