sql_target_mb with max server memory, then read the size of the grant pool. If SQL Server’s target is far below the cap and the pool is a few MB, raising max server memory does nothing: the pool is sized from the target, not the cap. Confirm the query is waiting for a grant, free memory on the host, and cap the one query you need now while you do.Queries wait on RESOURCE_SEMAPHORE even though SQL Server is well below max server memory. Check whether low Windows memory has shrunk the grant pool before raising the cap. For other causes, see Troubleshoot RESOURCE_SEMAPHORE waits.
Confirm the Query Is Waiting for a Grant
One row per query that has asked for workspace memory and been given none. sys.dm_exec_query_memory_grants keeps grant_time NULL until the grant arrives.
SELECT
r.session_id,
r.status,
r.wait_type,
r.cpu_time AS cpu_ms,
r.total_elapsed_time / 1000 AS elapsed_sec,
g.requested_memory_kb,
g.granted_memory_kb,
g.grant_time
FROM sys.dm_exec_requests AS r
JOIN sys.dm_exec_query_memory_grants AS g
ON g.session_id = r.session_id
AND g.request_id = r.request_id
WHERE g.grant_time IS NULL
OPTION (MAXDOP 1); -- needs no memory grant, so it cannot queue behind the queries it is looking for
16 ms of CPU in 50 seconds is a query that has not started, not a slow one. It asked for 52,848 KB and has been given nothing.
OPTION (MAXDOP 1) on both queries on this page. Run in parallel on the lab, the pool query asked for a 3,592 KB grant of its own and queued 107 seconds behind the query it was meant to be looking at. Serial, it needed no grant.Read the Grant Pool Against the Cap
This is the check that makes this a different problem. One row: the cap, what SQL Server is aiming to hold now, the most the grant pool could grow to, and the size it is right now.
SELECT
c.max_server_mb,
i.committed_target_kb / 1024 AS sql_target_mb,
s.max_target_memory_kb / 1024 AS pool_max_mb,
s.target_memory_kb AS pool_now_kb,
s.grantee_count,
s.waiter_count,
p.process_physical_memory_low AS memory_low,
m.available_physical_memory_kb / 1024 AS os_free_mb
FROM sys.dm_exec_query_resource_semaphores AS s
CROSS JOIN sys.dm_os_sys_info AS i
CROSS JOIN sys.dm_os_process_memory AS p
CROSS JOIN sys.dm_os_sys_memory AS m
CROSS JOIN (SELECT CAST(value_in_use AS int) AS max_server_mb
FROM sys.configurations
WHERE name = 'max server memory (MB)') AS c
WHERE s.resource_semaphore_id = 0 -- the regular semaphore, not the small-query one
AND s.pool_id = 2 -- the default pool; drop this line if you use Resource Governor
OPTION (MAXDOP 1); -- needs no memory grant, so it cannot queue behind the queries it is looking for
The cap is 2,048 MB. SQL Server is aiming for 442 MB, the pool could grow to 332 MB, and right now it is 10,560 KB. The waiting query wants 52,848 KB, 5 times the whole pool, so it waits until the pool grows or the request is cut.
pool_max_mb follows sql_target_mb, not the cap: 332 of 442 MB here, and 840 of 1,160 MB when the same instance had memory back. sql_target_mb is committed_target_kb, which SQL Server works out from the load and the memory on the machine, not from the setting alone.
memory_low = 1 is process_physical_memory_low: SQL Server responding to a low physical memory notification from Windows. While it holds, the target stays down and the pool with it. Minutes apart on the same instance, with no setting changed, the same query returned memory_low 0, a target of 1,160 MB and a pool of 618,112 KB, and the same sort ran straight away.
Which Case You Have
| What the pool row shows | What to do |
|---|---|
memory_low 1, sql_target_mb far below max_server_mb, pool a few MB | Windows is short of memory and SQL Server has given its share back. Free memory on the host: another instance, containers, a VM, a backup or antivirus agent. Leave max server memory alone. |
memory_low 0, sql_target_mb at or near the cap, waiters behind large requests | Ordinary grant pressure inside SQL Server. Statistics, indexes and how many big queries run at once: Troubleshoot RESOURCE_SEMAPHORE Waits. Raise the cap only if the host has memory to spare. |
Let One Query Run While You Free Memory
The fix is on the host. While you get memory back, a query you need now can be capped so its request fits the pool. MAX_GRANT_PERCENT is a percentage of the per-query limit, and that limit is REQUEST_MAX_MEMORY_GRANT_PERCENT of the pool, 25 by default.
-- the lab query; put the hint on the query you need to run now
SELECT OrderId, CustomerRef, Notes
FROM dbo.Orders
ORDER BY CustomerRef
OPTION (MAX_GRANT_PERCENT = 10);
| Hint | Requested | Grant wait | Sort spilled to tempdb |
|---|---|---|---|
| none | 52,848 KB | still queued after 100 seconds, cancelled | never ran |
MAX_GRANT_PERCENT = 10 | 8,480 KB | 3 ms | 3,940 pages |
MAX_GRANT_PERCENT = 2 | 1,696 KB | 0 ms | 5,498 pages |
Read from the actual execution plans on the lab against a 10,560 KB pool. The plans put the per-query limit at about 85 MB, a quarter of the most the pool could grow to, which is how one request can be bigger than the whole pool as it stands. The hint brings the request under the pool, and the price is the spill. Take it out once the host has memory again.
What Will Not Help
- Raising max server memory. SQL Server is not reaching the cap it already has.
- Killing the waiters. The queue empties, the pool stays the same size, and the next query that needs a grant waits the same way.
- Tuning the query that noticed. A small diagnostic stuck in the queue is a symptom of the pool, not the cause.
Frequently Asked Questions
Will raising max server memory fix RESOURCE_SEMAPHORE waits?
Only when SQL Server is already at the cap and the host has memory to spare. Check sql_target_mb first. On the lab the cap was 2,048 MB and the target 442 MB, so a higher cap would have changed a number SQL Server was not using.
Why does one query ask for more memory than the whole pool?
The per-query limit is worked out from the most the pool could grow to, not from its size right now. On the lab that limit was about 85 MB while the pool stood at 10,560 KB, so a 52,848 KB request was allowed and could not be served.
Can I just kill the sessions that are waiting?
You can, and the queue clears. It fixes nothing, because the pool is still the size it was, and scheduled jobs come back and queue again. Kill a waiter only to get a specific session out of the way.
My forced_grant_count is in the thousands. Is that the cause?
It is not a live reading. forced_grant_count and timeout_error_count in sys.dm_exec_query_resource_semaphores count up from the last restart, so they say the instance has been short of grant memory at some point. The live figures are grantee_count and waiter_count.
Should I lower REQUEST_MAX_MEMORY_GRANT_PERCENT instead of using the hint?
It does the same job for every query in a Resource Governor workload group, and it is a server change rather than a change to one query. When the cause is memory on the host, neither is the fix: they make requests small enough to fit a pool that should not be that small.
Why are SQL Agent jobs queued as well?
Everything in the same resource pool draws grants from the same semaphore, whatever submitted it. Agent jobs, SSMS and monitoring queries all wait in one queue, and a small request can sit behind a large one at the front.
Is this the same as RESOURCE_SEMAPHORE_QUERY_COMPILE?
No. RESOURCE_SEMAPHORE_QUERY_COMPILE is a separate semaphore for compile memory, so the query has not finished compiling yet. Same family, different bottleneck.
This page is the below-the-cap case. These cover the rest of the wait, and the scripts that read the same numbers.
- Troubleshoot RESOURCE_SEMAPHORE Waits, the full workflow, when SQL Server is at its cap and the queries are the problem.
- RESOURCE_SEMAPHORE Wait Type in SQL Server, the library entry for the wait and its causes.
- Errors 701 and 8645, what the client sees when a grant wait runs out of time.
- Get Memory Configuration and Usage, the cap, the RAM on the box and what SQL Server holds, in one pass.
- Get Query Performance Deep-Dive, Get-MemoryGrantSpills, granted against used per query from the plan cache.
- Get Trace Flags and Resource Governor Configuration, whether a workload group has a different grant limit.
- Get Statistics Health, the first stop when the grants are oversized rather than the pool undersized.
- Get Wait Statistics, how much of the instance’s waiting this has been, over a window you choose.
- Get Open Transactions, what else is holding work open while the queue builds.
- SQL Server Wait Types Library, every wait type explained.
- Performance and Troubleshooting, the pillar this sits in.
Leave a Reply