不是聚合本身吃光tempdb,而是group by或order by触发的sort/hash match在内存不足时溢出到磁盘;需通过sys.dm_db_session_space_usage查高internal_objects_alloc_page_count会话,结合sys.dm_exec_requests的sort等wait_type及执行计划中spilllevel="2"定位。

直接结论:不是聚合本身吃光 tempdb,而是 GROUP BY 或 ORDER BY 触发的排序(Sort)或哈希(Hash Match)操作在内存不足时溢出到磁盘——必须从执行计划溢出点入手定位和干预。
怎么快速确认是聚合操作导致 tempdb 溢出?
别查临时表或日志文件,先盯住运行中会话的内部对象分配量:
- 运行
SELECT session_id, internal_objects_alloc_page_count FROM sys.dm_db_session_space_usage WHERE internal_objects_alloc_page_count > 100000,重点关注高值 session - 把该
session_id带入sys.dm_exec_requests查wait_type:出现SORT、PAGEIOLATCH_IO或SOS_SCHEDULER_YIELD是强信号 - 用
sys.dm_exec_query_plan提取执行计划 XML,搜索<relop.></relop.>或<relop. match></relop.>,再看是否有SpillLevel="2"或警告文字 “Operator used tempdb”
为什么加了索引,GROUP BY 还是走 Sort?
索引对排序没有“自动跳过”效果;优化器是否走索引排序,取决于统计信息质量、行数估算和连接顺序:
- 检查
GROUP BY列的统计信息是否陈旧:DBCC SHOW_STATISTICS('orders', 'IX_orders_user_id')中Rows和Rows Sampled差距过大,就该UPDATE STATISTICS - 如果
GROUP BY列不在索引最左列,或索引包含列不覆盖 SELECT 列表,仍可能触发 Sort - WHERE 条件写在外层(如
SELECT user_id, COUNT(*) FROM orders JOIN users ... WHERE orders.status = 1 GROUP BY user_id),优化器可能误判基数,放弃索引扫描
聚合查询的实操优化路径
目标不是“避免 GROUP BY”,而是让排序/哈希尽可能留在内存中,或缩小输入规模:
- 强制下推过滤:把
WHERE放进子查询,例如GROUP BY前先JOIN (SELECT * FROM orders WHERE order_date >= '2025-01-01') o ON ... - 补覆盖索引:对
orders(user_id, status, order_date)建非聚集索引,让GROUP BY user_id可以走索引有序扫描,免 Sort - 拆分大聚合:用
TOP N+OFFSET分页聚合,或按时间/分区键分批处理,降低单次内存压力 - 紧急干预:若已发现某 session 的
internal_objects_alloc_page_count持续飙升,立刻KILL [session_id],比等它填满 tempdb 更稳妥
tempdb 配置与监控不能只靠收缩
DBCC SHRINKFILE 对活动 spill 查询基本无效;真正要落地的是资源边界控制和日常基线观测:
- SQL Server 2025+ 可用资源调控器限制工作负荷组:
ALTER WORKLOAD GROUP wg_adhoc WITH (GROUP_MAX_TEMPDB_DATA_MB = 2048) - 每天跑一次
SELECT SUM(version_store_reserved_page_count) FROM sys.dm_db_file_space_usage,持续上涨说明长事务或快照隔离在囤积版本记录 - tempdb 文件数量建议设为 CPU 核心数(≤8),每个初始大小一致、禁用自动增长——避免碎片和争用
最难处理的永远是那些没报错、没超时、但悄悄把 hash 表写满磁盘的聚合查询:它们不触发明显等待,却持续拖慢整个实例。盯紧 sys.dm_db_session_space_usage 的增量变化,比等错误报警更有效。











