根本原因不是内存不足,而是查询优化器预估的内存需求远超实际需要,导致排队等待授权;典型诱因包括统计信息过时、函数包裹分组字段、TOP/OFFSET写法及高基数估算错误。

根本原因不是内存不足,而是查询优化器预估的内存需求远超实际需要,导致排队等待授权
SQL Server聚合查询为什么会触发MEMORYGRANT_WAIT
当你看到 MEMORYGRANT_WAIT 等待类型频繁出现,尤其集中在 SORT、HASH MATCH、STREAM AGG 等操作上,说明 SQL Server 正在为聚合(如 GROUP BY、ORDER BY、COUNT(DISTINCT))申请内存时被卡住。这不是“没内存了”,而是:优化器基于过时统计信息或低效写法,高估了排序/哈希所需内存,于是把该查询塞进一个大内存队列里等授权;而真正需要小内存的查询反而被压在后面。
- 典型诱因包括:未更新统计信息、
TOP+ORDER BY无索引、GROUP BY字段存在大量重复值但基数估算错误、使用CONVERT或函数包裹分组字段导致统计不可用 - 注意:
MEMORYGRANT_WAIT和RESOURCE_SEMAPHORE是不同层级的等待——前者是“已排队等发内存”,后者是“连队列都进不去”,后者才真代表内存池整体紧张 - 如果同时观察到
sys.dm_exec_query_memory_grants中grant_time为空、requested_memory_kb远大于granted_memory_kb,基本可确认是预估失准而非物理内存短缺
如何快速验证是不是统计信息问题
执行聚合前先查对应表和字段的统计信息是否陈旧:
DBCC SHOW_STATISTICS('Orders', 'IX_OrderDate_CustomerID');
重点关注 rows(统计创建时行数)与当前 SELECT COUNT(*) FROM Orders 的偏差。若偏差 >20%,或 modification_counter 显著高于阈值(默认约 20% 行变更),就该更新:
- 用
UPDATE STATISTICS Orders WITH FULLSCAN, NORECOMPUTE;强制全扫(适合夜间维护窗口) - 日常可用
UPDATE STATISTICS Orders WITH SAMPLE 30 PERCENT;加速 - 避免只更新单个统计对象却忽略列级统计,尤其是
GROUP BY中的组合字段
聚合写法本身会放大内存预估偏差
以下写法会让优化器完全无法准确估算中间结果集大小,直接按最坏情况分配内存:
-
SELECT COUNT(DISTINCT CONCAT(FirstName, LastName)) FROM Customers—— 函数包裹导致统计失效 -
SELECT CustomerID, SUM(Amount) FROM Sales GROUP BY CustomerID ORDER BY SUM(Amount) DESC OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY——OFFSET/FETCH要求先完成全部聚合再截断,优化器必须按全量预估 WHERE Date >= '2025-01-01' AND Date ——GETDATE()是运行时函数,统计无法绑定日期范围
改法示例:
DECLARE @asof DATE = GETDATE();<br>SELECT CustomerID, SUM(Amount)<br>FROM Sales<br>WHERE Date >= '2025-01-01' AND Date GROUP BY CustomerID<br>ORDER BY SUM(Amount) DESC<br>OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY;
tempdb争用常被误认为内存问题
聚合中大量使用 HASH 或 SORT 操作时,若内存不足,SQL Server 会 spill 到 tempdb。此时你可能同时看到 IO_COMPLETION + MEMORYGRANT_WAIT,但根因是 tempdb 文件配置不合理:
- 单个
tempdb数据文件,且未按 CPU 核数配置(如 8 核服务器只配 1 个文件)→ 触发PAGELATCH_UP争用 -
tempdb文件初始大小太小,自动增长频繁 → 大量WRITELOG和ASYNC_NETWORK_IO连带升高 - 解决方案:按核心数配置多个等大小的
tempdb数据文件(上限通常 8 个),关闭自动增长或设为固定增量(如 512MB)
真正难处理的是那种“看起来内存够、统计也新、写法也规范,但一跑聚合就卡在 MEMORYGRANT_WAIT”的情况——这时候得看查询计划里的 RequestedMemory 和 GrantedMemory 差值,再结合 sys.dm_exec_query_resource_semaphores 的 max_target_memory_kb 是否被其他大查询长期占满。这种场景下,往往不是调优能解决的,而是要拆分聚合粒度或引入物化中间结果。










