Do You Believe?
Memory is so very important to SQL Server performance. It doesn’t solve every problem, but you sure as heck feel it when there’s not enough. Like I said before, memory is a shared resource.
Your buffer pool needs it to cache data pages, and your queries need it for operators that need extra space to write stuff down.
If you constantly have queries and the buffer pool fighting over memory, sure, you might just need more memory. You might also need to grapple with those memory grants in a different way.
You & Me
To recap earlier posts, the most common things you’ll see asking for memory grants in a query plan are Sorts and Hashes. There is one form of optimized Nested Loops that’ll ask for memory, but I don’t see people running into problems with those all too often.
Let’s start with a simple query that will ask for a memory grant to sort data.
SELECT * FROM ( SELECT TOP (1000) u.Id -- 166MB (INT) , u.DisplayName -- 300MB (NVARCHAR 40) , u.WebsiteUrl -- 900MB (NVARCHAR 200) , u.Location -- 1.3GB (NVARCHAR 100) , u.AboutMe -- 9.4GB (NVARCHAR MAX) FROM dbo.Users AS u ORDER BY u.Reputation ) AS u OPTION(MAXDOP 1, RECOMPILE);
Now, in a previous post I explained how the optimizer can make some weird guesses about memory grant needs when you get strings involved — not ordering by strings, just selecting them.
The guess made is that string columns will be half full. That can work out okay if your string-column-fullness hovers around the middle ground.
But if it hovers towards the high or low end of how you’ve defined the length of your string columns, you can end up with some crappy memory need estimates.
In the last post, we looked at how much the optimizer would ask for. Now let’s look at how much actually gets used.
For the full query, the memory grant is 9.4GB, but only 380MB gets used. That seems bad to be.
Now, to be fair, the nice people at Microsoft are attempting to right these wrongs. In SQL Server 2017 we got Memory Grant Feedback, but it was only available for queries with Eau De Batch Mode on.
In SQL Server 2019, it’s available to Row Mode plans. Of course, that’s only in Enterprise Edition. If you’re on Standard Edition, you’re screwed :^)
So for all you screwed folks out there, which is most of you, you might have queries constantly asking for way more memory than they need. In this case, the query asks for about 9.1GB of memory more than it needs.
“Just Select Less”
Well, okay. What if we don’t select that MAX column? All your tables are well-designed, no one has used longer string columns than they should have, and none of your queries select a bunch of columns they don’t need.
SELECT * FROM ( SELECT TOP (1000) u.Id -- 166MB (INT) , u.DisplayName -- 300MB (NVARCHAR 40) , u.WebsiteUrl -- 900MB (NVARCHAR 200) , u.Location -- 1.3GB (NVARCHAR 100) FROM dbo.Users AS u ORDER BY u.Reputation ) AS u OPTION(MAXDOP 1, RECOMPILE);
We’ll still ask for 1.3GB of memory, and only use a fraction of it.
What’s interesting is that in total we only use about 100MB less memory without the AboutMe column in there, despite asking for about 8GB less memory.
Reducing Memory Grants
There are a number of ways to help mitigate queries asking for too much memory, but they all depend on what the root cause of the issue is.
- You might have overly large columns, or queries that do something like select * — you might be able to fix that by rewriting queries. Given enough control, you should resize columns to be a more appropriate size, too.
- You might be dealing with parameter sniffing, which has a whole bunch of different approaches which will require their own decades of research, ha ha ha.
- You might be missing an opportune index that could help you avoid a sort, or help the optimizer choose a non-hash join or aggregate.
- You might be asking queries to put data in order when it’s not necessary
- You might be using some unfortunate “feature” that messes up cardinality estimation
- You might be dealing with out of date statistics
- You might need to apply a query hint to control the size of the grant
- You might need to use Resource Governor to control the size of memory grants
Of course, all this effort depends on how severe your problems are.
A good way to judge if queries are trampling on each other is to look at resource semaphore waits. That’s queries waiting to get memory to run.
Another might be to look at PAGEIOLATCH_XX waits to see how much time you’re spending reading pages from disk. It’s possible that excessive query memory grants are forcing data out of the buffer pool.
Thanks for reading!
If this is the kind of SQL Server stuff you love learning about, you’ll love my training. I’m offering a 75% discount to my blog readers if you click from here. I’m also available for consulting if you just don’t have time for that and need to solve performance problems quickly.