group by 查询慢主因是未走索引导致worktable或hash match(aggregate);应查执行计划中sort/hash match及“operator used tempdb”警告,结合set statistics io on确认逻辑读是否远超表行数,并按“过滤→关联→分组”顺序建覆盖索引。

GROUP BY 查询慢,八成不是写法问题,而是执行计划里出现了 Worktable 或 Hash Match (Aggregate) —— 这说明 SQL Server 没法用索引直接分组,被迫建临时表、排序、哈希桶,IO 和 CPU 都扛不住。
怎么一眼看出 GROUP BY 是不是真走索引
别只看“用了索引”,要看执行计划里有没有 Sort、Hash Match,以及警告里有没有 Operator used tempdb。再跑一次 SET STATISTICS IO ON,如果 logical reads 是表行数的几倍甚至几十倍(比如 500 万行表扫出 2000 万逻辑读),基本就是分组过程反复读写临时页。
- 确认索引是否覆盖:
GROUP BY a, b, c+SUM(d),索引必须是(a, b, c) INCLUDE (d),缺一个字段都可能触发回表或临时表 - 跨表分组时,JOIN 字段的索引要匹配:比如
JOIN Customers ON o.CustomerID = c.CustomerID,Customers表上CustomerID得是主键或唯一索引,否则关联后仍要查CustomerName和City - WHERE 条件字段没进索引前导列?比如
WHERE OrderDate BETWEEN ... AND ...却只建了(CustomerID)索引,那扫描还是全表起步
建什么索引才真正管用
联合索引顺序不是按 GROUP BY 列顺序拼,而是按「过滤 → 关联 → 分组」三步走。比如查询是:WHERE OrderDate >= '2024-01-01' AND Status IN ('Shipped','Completed') 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放最前:让 WHERE 范围扫描尽可能早缩小数据集,后面分组压力才小 -
CustomerID紧跟其后:扫描结果天然按分组键有序,避免额外Sort -
INCLUDE (Amount):把聚合字段带上,SUM(o.Amount)直接从索引读,不回表 -
Customers表上得有CustomerID主键,且CustomerName、City要么加到INCLUDE,要么单独建索引,否则 JOIN 后还得查数据页
DISTINCT 真能秒换 GROUP BY?
当你要的只是“有哪些组合”,而不是“每个组合的 SUM/COUNT”,DISTINCT 常比 GROUP BY 快得多——SQL Server 对 DISTINCT 更倾向用 Sort + 去重,而 GROUP BY 默认尝试哈希聚合,一卡就是几十秒。
- 适用场景:
SELECT DISTINCT o.CustomerID, c.CustomerName, c.City FROM ... WHERE ...(无聚合函数) - 不适用场景:
SELECT o.CustomerID, COUNT(*) FROM ... GROUP BY o.CustomerID—— 这里DISTINCT根本不能替代 - 注意语义陷阱:如果某组有 100 行,
DISTINCT只返回一行;GROUP BY可以同时返回COUNT(*),这是本质区别
批模式(Batch Mode)到底启没启用
SQL Server 2019 的批模式不是自动开的,得满足硬条件:中间结果集 > 10 万行、聚合列有合适索引、且不能有阻断运算符。执行 SET STATISTICS XML ON 后看执行计划里 Aggregate 节点的 ActualExecutionMode,是 Batch 才算生效。
- 常见拦路虎:
GROUP BY DATEPART(year, create_time)(函数包裹)、SELECT ... NVARCHAR(MAX)(大字段)、WHERE a = ? OR b = ?(未括号导致谓词下推失败) - 别乱加
OPTION (MAXDOP 4):手动设并行度容易打断批模式调度,小查询反而更慢 - 真要调并行,改服务器级配置:
sp_configure 'cost threshold for parallelism',默认 5 太低,OLAP 类 GROUP BY 可试 25–50
最常被忽略的点:索引建了但顺序错了,或者 INCLUDE 漏了聚合字段,看着走了索引,实际还是在磁盘上反复造临时表。优化 GROUP BY,本质是让数据流“进来就有序、计算不离索引、输出不排序”。










