先看执行计划中是否出现index seek或clustered index seek,若为table scan或clustered index scan则未走索引;再检查group by字段是否为索引前导列、有无隐式转换或函数包裹导致索引失效;最后确认using temporary或hash match(aggregate)是否因缺少覆盖索引而触发临时表。

怎么看执行计划里聚合操作是否走索引
聚合查询慢,第一反应不是加索引,而是先确认执行计划里 GROUP BY 和 WHERE 是否真的用了索引。关键看两个节点:Index Seek 或 Clustered Index Seek 出现在聚合前,且扫描行数(Actual Rows)接近过滤后数据量;反之如果出现 Table Scan 或 Clustered Index Scan,说明没走索引或索引失效。
- 检查
GROUP BY字段是否是索引的前导列——比如索引是(status, created_at, amount),但查询按created_at分组,就无法利用该索引 - 注意隐式转换:若
WHERE条件里写成WHERE customer_id = '123'(字符串),而字段是INT,会导致索引跳过,执行计划里会显示CONVERT_IMPLICIT - SQL Server 中,如果聚合字段被函数包裹(如
GROUP BY YEAR(order_date)),除非建了计算列+索引,否则必然退化为扫描
为什么 GROUP BY 后出现 Using temporary 或 Hash Match (Aggregate)
Using temporary(MySQL)或 Hash Match (Aggregate)(SQL Server)不是错误,但它是性能分水岭:意味着数据库必须把中间结果暂存内存/磁盘再分组。这通常发生在索引无法覆盖全部过滤+分组+聚合字段时。
- 当
SELECT中的非分组字段没出现在索引里(例如查customer_name, SUM(amount)但索引只有customer_id),就会触发临时表 - SQL Server 的
Hash Match成本高,尤其当右输入(即待聚合的数据集)远超work_mem(或hash warning提示)时,会 spill 到磁盘,速度骤降 - PostgreSQL 中若
EXPLAIN ANALYZE显示Sort + GroupAggregate,说明没走索引排序,而是在内存排序后再聚合——这时加个(group_col, sort_col)索引常能转成GroupAggregate直接流式处理
如何验证索引是否真正“覆盖”聚合查询
覆盖索引不是“有索引就行”,而是要让优化器完全不需要回表读原行。判断标准很简单:执行计划里所有涉及该表的输出字段(包括 GROUP BY 列、WHERE 过滤列、SELECT 中的聚合列)都必须出现在同一个索引定义中。
- 例如查询
SELECT region, COUNT(*), AVG(price) FROM products WHERE category = 'electronics' GROUP BY region,理想索引是CREATE INDEX idx_cat_reg_price ON products(category, region, price) - 少一个字段(比如漏了
price),AVG(price)就得回表取值,执行计划会出现Key Lookup或Clustered Index Seek配合主键查找 - SQL Server 可用
sys.dm_db_index_usage_stats查该索引的user_seeks是否增长,避免建了却没被用
聚合查询执行计划里最易被忽略的三个信号
很多人只盯 Rows 和 Cost,但以下三点才是真瓶颈线索:
-
Actual Rows远大于Rows(比如预估 100,实际 50 万):统计信息过期,立刻跑UPDATE STATISTICS或ANALYZE TABLE,别调索引 - 聚合节点上出现
Warning: Type conversion或Convert操作:说明字段类型不一致,强制转换阻断索引使用 -
Estimated Subtree Cost最高的节点不是扫描,而是Compute Scalar或Stream Aggregate:说明 CPU 成为瓶颈,可能因聚合字段数据类型过大(如TEXT、XML)或表达式太复杂(如嵌套CASE WHEN)
执行计划不是看一次就完事的文档,而是每次改查询、加索引、更新数据后都要重跑 EXPLAIN ANALYZE 或 SET STATISTICS XML ON 对比的实际日志。尤其聚合类查询,行数误差一倍,性能可能差十倍。











