HashAggregate吃光内存是因为它需全量构建哈希表,不流式处理,分组键基数高、字符串或多列复合分组加剧膨胀,且并行下各线程独立建表;确认信号是执行计划中GrantedMemory_KB远小于RequiredMemory_KB、SpillLevel>0或TempDbPages非零。

HashAggregate 为什么吃光内存?
因为 SQL Server 的 HashAggregate 必须把所有待分组的行全读进内存,为每个分组键构建哈希桶(hash bucket),并在桶里维护聚合状态(如 SUM 累加器、COUNT 计数器)。它不流式处理,也不依赖顺序——数据一进来就散列、就分配内存,直到扫完才输出结果。
- 分组键基数越高(比如
user_id百万级不同值),哈希表膨胀越快,内存占用≈行数 ×(键宽 + 状态大小 + 哈希结构开销) - 字符串字段(如
email或product_name)做分组时,比整型多出数倍哈希计算和存储开销 - 如果
GROUP BY含多个列(如a, b, c),哈希键是拼接后的复合值,宽度直接叠加 - 并行执行下,每个线程都独立建一份哈希表,总内存消耗 = 线程数 × 单表内存
怎么确认真是 HashAggregate 在爆内存?
别看任务管理器或 sys.dm_os_memory_clerks,它们反映的是实例级缓存,和这个查询无关。真正信号在执行计划里:
在 macOS 上通过 LaunchAgent 安装、更新、运行和移除 OpenClaw Gateway Monitor + Gateway Watchdog。适用于用户请求一键部署监控的场景。
- XML 执行计划中搜
GrantedMemory_KB和UsedMemory_KB:若后者接近或超过前者,说明已撑满配额,开始刷磁盘 - 执行计划节点标红警告
Warning: Memory Grant Warning,基本等于“已降级到 tempdb” -
sys.dm_exec_query_memory_grants中查该查询:若granted_memory_kb远小于required_memory_kb(差 30%+),就是内存授予严重不足 - 执行计划里出现
Hash Match (Aggregate)+TempDbPages非零,或SpillLevel > 0
为什么调 max server memory 没用?
SQL Server 不靠全局内存池跑 HashAggregate,它用的是单次查询的「内存授予」(query memory grant),由优化器基于行数估算 + 聚合复杂度动态申请。哪怕你把 max server memory 设到 90%,只要优化器估错基数(比如统计信息过期),它照样只敢要 4MB,然后溢出。
-
min server memory是保底值,不是分配上限;设太高反而让其他组件抢不到内存 - 盲目开
MAXDOP 0会让小查询抢光 grant,大查询排队等内存,而不是更快 -
QUERYTRACEON 8649是未公开调试标记,2019+ 版本可能污染 plan cache,别碰
真正能压内存的三件事
核心思路是:少读行、少建桶、少存状态。
- 强制走
Stream Aggregate:加ORDER BY且必须与GROUP BY完全一致(列名、顺序、方向),例如GROUP BY region, status ORDER BY region, status;有索引支撑时,排序可免,直接流式聚合 - 前置过滤:把
WHERE created_at >= '2024-01-01'写在GROUP BY前,别等百万行全拉进来再筛 - 精简分组字段:用
user_id替代user_email,用TINYINT状态码替代VARCHAR(20);避免GROUP BY SUBSTRING(name, 1, 5)这类无法走索引的表达式
HashAggregate 的内存压力不是“不够用”,而是“不该让它用”。一旦输入行数减半,哈希表体积往往缩到 1/4 以下——过滤永远比调内存参数见效快。

















