WindowAgg后总跟着Spool是因为SQL Server无法流式计算窗口逻辑,被迫将中间结果暂存至tempdb;主因是PARTITION BY列无索引、RANGE帧遇重复值、数据未按分区/排序字段物理有序。

为什么WindowAgg后面总跟着Spool?
这不是语法写错了,是SQL Server发现无法流式计算窗口逻辑,被迫把中间结果暂存到tempdb——内存不够时直接落盘。关键诱因有三个:PARTITION BY列没索引、ORDER BY字段存在大量重复值且用了RANGE帧、窗口帧过大但数据未按分区/排序字段物理有序。
建索引必须严格匹配PARTITION BY + ORDER BY顺序
SQL Server不会为每个查询动态重排数据,它只信任索引定义的顺序。单列索引根本无效:
- 错误写法:
CREATE INDEX ON orders(user_id)或CREATE INDEX ON orders(order_date) - 正确写法:
CREATE INDEX orders_user_date_idx ON orders(user_id, order_date) INCLUDE (amount) - 第一列必须是
PARTITION BY字段,第二列必须是ORDER BY字段;INCLUDE后跟SELECT中用到的其他列,避免回表 - 如果同时有
PARTITION BY A, B和ORDER BY C,索引应为(A, B, C)INCLUDE (D, E),顺序不能颠倒
用ROWS代替RANGE,尤其在非唯一排序列上
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW会按值“等价聚合”,遇到重复ORDER BY值(比如截断日期、状态码)就把所有同值行纳入当前窗口——引擎必须缓冲并扫描更多行来确认边界,极易触发Spool。
- 时间序列或主键递增场景,一律用
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW——它不依赖值唯一性,性能可预测 - 只有明确需要“同值同行一起算”时才用
RANGE,例如按评分分档统计人数,且评分字段已建索引 -
RANGE在datetime2或浮点列上极易出错;SQL Server 2019 不支持RANGE与LEAD/LAG混用,会报错Msg 116
避免嵌套窗口或多次引用同一窗口定义
写成这样:SUM(Amount) OVER (PARTITION BY Dept ORDER BY Date) AS s1, AVG(Amount) OVER (PARTITION BY Dept ORDER BY Date) AS s2,SQL Server会分别执行两次窗口计算,而不是复用中间结果。
- 改用CTE预计算一次:
WITH win AS (SELECT ..., SUM(Amount) OVER (...) AS s1, AVG(Amount) OVER (...) AS s2 FROM ...) - 或者用子查询包裹,确保窗口逻辑只跑一遍
- 检查执行计划:若看到多个
Window Spool或重复Sort节点,基本就是这个原因 - 注意:CTE本身不物化,但配合
OVER能强制优化器复用计算路径
最隐蔽的坑是低基数PARTITION BY——比如只按Status(仅3个值)分区,整个表被压成3个巨分区,内存很快溢出到tempdb。务必先用SELECT COUNT(*) GROUP BY [partition_col]看分布,单个分区超100万行就要警惕。

















