SQL Server 2019 无 EXPLAIN,需用 SET STATISTICS XML ON 或 PROFILE ON 获取实际执行计划;Rebinds>1 表明相关子查询被反复执行,Compute Scalar+Table Spool 暗示不可缓存;物化临时表可规避嵌套误判。

直接看 EXPLAIN ANALYZE,别只盯 SELECT 执行结果
SQL Server 2019 不叫 EXPLAIN,而是用 SET STATISTICS XML ON 或图形化执行计划(Ctrl+M)——但真正能暴露嵌套子查询真实行为的,是 EXPLAIN ANALYZE 的等价物:SET STATISTICS PROFILE ON 配合实际执行。光看语法是否通过、返回几行数据,完全掩盖问题。很多“查不到数据”或“慢得离谱”,其实是子查询被反复执行、或根本没走索引。
- 先关掉
SET STATISTICS IO OFF和SET STATISTICS TIME OFF,避免干扰 - 运行前加
SET STATISTICS XML ON,执行后在“执行计划”标签页里找带SELECT节点的 XML,重点搜Dependent、Unmatched、Rebinds - 如果看到某个子查询节点的
Rebinds值远大于 1(比如 10000),说明它被外层每行都重算了一次——这就是相关子查询雪崩的铁证
识别 DEPENDENT SUBQUERY 和 UNCACHEABLE SUBQUERY
SQL Server 执行计划 XML 中不会直写 “DEPENDENT SUBQUERY”,但会通过 RelOp 节点的 PhysicalOp 和属性暴露本质:当子查询出现在 Compute Scalar 下、且其 EstimateRebinds > 0,或嵌套在 Nested Loops 内部循环侧,基本就是依赖型子查询。而 UNCACHEABLE SUBQUERY 在 SQL Server 里常表现为 Compute Scalar + Table Spool (Lazy Spool) 组合,意味着每次调用都重新计算、无法复用。
-
WHERE id IN (SELECT user_id FROM logs WHERE status = u.status)这类带外层列引用的,必触发EstimateRebinds> 0 - 若子查询含
GETDATE()、NEWID()或未参数化的变量(如直接拼字符串),会被标记为Uncachable,强制不缓存 - 不要依赖“看起来像 JOIN 就安全”——哪怕写成
FROM t1 CROSS APPLY (SELECT ...) AS x,只要子查询里引用了t1.col,仍可能被反复执行
用临时表物化中间结果,绕过执行计划误判
SQL Server 优化器对三层以上嵌套(尤其含 GROUP BY / ORDER BY)容易放弃最优路径,不是你写得错,是它“算不过来”。与其反复调参,不如把最内层稳定子查询拎出来,显式物化。
- 用
SELECT ... INTO #tmp FROM (...)替代最深那层子查询,再在外层JOIN #tmp—— 这样执行计划彻底扁平,索引、统计信息、行估算全可控 - 注意
#tmp表必须建好索引(哪怕只是CREATE CLUSTERED INDEX IX_tmp_id ON #tmp(id)),否则 JOIN 时又退化成哈希匹配或嵌套循环 - 如果子查询结果集小(OPTION (RECOMPILE) 可让优化器按实际行数生成计划;但别滥用,编译开销本身也吃 CPU
CTE 不自动物化,多次引用照样重复计算
很多人以为把嵌套子查询改成 CTE 就“优化了”,但在 SQL Server 2019 默认行为下,CTE 是纯逻辑重写,不是物理缓存。一个 CTE 被引用 3 次,很可能执行 3 次——除非满足极少数条件(如被优化器判定为“易物化”且成本低)。
- 验证方法:在 CTE 后加
SELECT * FROM cte_name单独跑一次,再跑完整语句,对比 I/O(SET STATISTICS IO ON)——如果后者逻辑读是前者的 2 倍以上,大概率重复执行了 - 想强制物化,唯一可靠方式是写成
SELECT ... INTO #cte_result FROM (your_cte) AS x,再后续引用#cte_result - 递归 CTE 除外:它天然需要迭代,但必须加
OPTION (MAXRECURSION n),否则默认 100 层就报Msg 530
调试嵌套子查询最易被忽略的一点:你以为在调子查询,其实瓶颈在外层 JOIN 顺序或缺失的统计信息更新。哪怕子查询单独跑得飞快,一旦嵌套进外层,SQL Server 可能因统计信息陈旧,错误估算行数,导致选择嵌套循环而非哈希连接——这时清空统计信息(UPDATE STATISTICS)比改写 SQL 更管用。

















