GROUP BY慢主因是未匹配执行流的联合索引导致临时表(Worktable)和哈希聚合,需按WHERE→JOIN→GROUP BY顺序建索引并覆盖分组列与INCLUDE字段,同时确保批模式触发条件与内存授予合理。

GROUP BY慢是因为用了临时表,不是索引没建全
执行计划里看到 Worktable、Hash Match (Aggregate) 或警告 Operator used tempdb,说明 SQL Server 没法直接按索引顺序分组,被迫在内存或磁盘上建临时结构。这不是“没加索引”,而是索引没对上分组逻辑——比如 GROUP BY o.CustomerID, c.CustomerName, c.City,但现有索引只覆盖 CustomerID 单列。
- 先跑
SET STATISTICS IO ON+ 查询,看logical reads是否远超表行数(5000 万行表读出 800 万页?大概率在扫临时结构) - 别信“已有索引”——跨表多字段分组时,必须确保所有
GROUP BY列都在同一联合索引里,且顺序与分组顺序一致 -
INCLUDE字段要覆盖所有聚合用到的列(如SUM(o.Amount)就得把Amount加进INCLUDE),否则仍会回表
建什么索引才真正起作用
有效索引不是按“分组字段拼起来”就行,得按 WHERE → JOIN → GROUP BY 的实际执行流排布。例如查询含 WHERE o.OrderDate BETWEEN @s AND @e AND o.Status IN ('Completed','Shipped'),再 JOIN Customers,最后 GROUP BY o.CustomerID, c.CustomerName, c.City,最优索引是:
CREATE INDEX IX_Orders_DateStatus_CustomerID_Incl ON Orders (OrderDate, Status, CustomerID) INCLUDE (Amount);
-
OrderDate和Status放最前:让范围扫描尽早砍掉 90% 数据,减少后续分组输入量 -
CustomerID紧跟其后:使扫描结果天然按分组键有序,避免额外Sort -
Customers表上必须有CustomerID主键(已有),但CustomerName和City需单独建索引或加进INCLUDE,否则JOIN后还得查
别让批模式(Batch Mode)失效
SQL Server 2019 的 Batch Mode on Rowstore 能把 GROUP BY 速度提几倍,但它很娇气——不满足条件就退化回低效的行模式。执行计划里 Aggregate 运算符的 ActualExecutionMode 显示 Row 就是没触发。
- 中间结果集得 >10 万行,小查询不会启用批模式
- 分组列不能套函数:
GROUP BY DATEPART(year, create_time)直接禁用批模式;改用WHERE create_time >= '2024-01-01'+ 原始列分组 - SELECT 列里别塞
NVARCHAR(MAX)、TEXT等大字段,它们会让引擎降级 - 别乱加
OPTION (MAXDOP 0)——并行度太高反而打散批处理粒度;MAXDOP 2或4更稳
内存不够时 GROUP BY 就卡住,不是 CPU 问题
查询卡在 30 秒不动,大概率是哈希分组内存不足,开始往 tempdb 刷数据。看执行计划里的 GrantedMemory_KB 和 UsedMemory_KB:如果后者接近前者,或者出现 Memory Grant Warning,就是内存瓶颈。
- 用
OPTION (MIN_GRANT_PERCENT = 30, MAXDOP 2)强制保底内存,比瞎调MAXDOP可靠 -
MIN_GRANT_PERCENT设太高(如 80)会导致其他查询排队,30–50 是较安全区间 - 统计信息过期会让优化器低估行数,错选
Sort + Stream Aggregate而非哈希——跑UPDATE STATISTICSwithFULLSCAN再试 -
QUERYTRACEON 8649是调试标记,2019+ 版本可能污染 plan cache,别用


















