相关子查询性能差的根本原因是每行主查询都触发一次内层执行,导致t2被扫描10万次;即使有索引,累积的索引查找开销仍远超哈希连接,且无法并行化。

子查询在 SQL Server 2019 中性能差,绝大多数情况不是语法写错了,而是执行计划被迫走嵌套循环 + 多次重复执行 —— 尤其是相关子查询(correlated subquery)。
为什么相关子查询会变慢
相关子查询每处理主查询的一行,就重新执行一次内层查询。比如 WHERE col IN (SELECT id FROM t2 WHERE t2.ref = t1.id),如果 t1 有 10 万行,t2 就可能被扫描 10 万次。
- SQL Server 不会自动把这类子查询“改写为 JOIN”,除非优化器能确认语义等价且索引可用
- 即使
t2.ref上有索引,每次执行仍要走一次索引查找(Key Lookup 或 Seek),开销累积后远超一次哈希连接 -
EXISTS比IN稍好(找到第一个匹配就退出),但仍是逐行驱动,无法并行化
用 JOIN 替代子查询的实操要点
把子查询逻辑显式转成 INNER JOIN 或 LEFT JOIN,让优化器有机会选择哈希/合并连接,并利用并行。
- 原写法:
SELECT * FROM orders o WHERE EXISTS (SELECT 1 FROM customers c WHERE c.id = o.cust_id AND c.status = 'active') - 改写后:
SELECT DISTINCT o.* FROM orders o INNER JOIN customers c ON o.cust_id = c.id WHERE c.status = 'active' - 注意加
DISTINCT:JOIN 可能因一对多产生重复行,而EXISTS天然去重 - 如果原意是“保留 orders 中所有行,只标出是否匹配”,就用
LEFT JOIN+WHERE c.id IS NOT NULL
用派生表(Derived Table)提前物化结果
当子查询本身不依赖外层字段(即非相关子查询),但结果集较大、又被多次引用时,提取成派生表可避免重复计算。
- 低效写法:
SELECT name, (SELECT AVG(salary) FROM emp) avg_sal FROM dept——AVG被算了dept行数次 - 高效写法:
SELECT d.name, a.avg_sal FROM dept d CROSS JOIN (SELECT AVG(salary) avg_sal FROM emp) a - 若子查询含聚合+分组,也适用:
FROM ... JOIN (SELECT dept_id, COUNT(*) cnt FROM emp GROUP BY dept_id) e ON ...
哪些子查询真的没法改写?盯住执行计划里的警告
有些场景改写成本高或语义难保,这时别硬改,优先看执行计划里有没有红色警告:
- 看到 “Warning: No Join Predicate” —— 检查是否漏写了
ON条件,导致笛卡尔积 - 看到 “Table Scan” 或 “Clustered Index Scan” 在子查询内层 —— 立刻检查
WHERE列是否缺索引,特别是关联字段和过滤字段 - 看到大量 “Compute Scalar” 或 “Constant Scan” —— 可能是子查询里用了函数(如
DATEADD、CONVERT),导致索引失效
真正难优化的,往往不是子查询本身,而是它暴露了底层缺失索引、数据类型不匹配、或统计信息陈旧这些更底层的问题。别只盯着“怎么改 SQL”,先跑一遍 SET STATISTICS IO ON 和 SET STATISTICS TIME ON,看清楚逻辑读和 CPU 时间到底耗在哪一层。










