Parameter Sensitivity Training Part 1
Video Summary
In this video, I delve into the intricacies of parameter sniffing and parameter sensitivity in SQL Server. Starting off with some humorous self-deprecating remarks about my past Data Community Summit 2025 presentation, I aim to make a complex topic accessible and engaging for viewers. We begin by defining key terms like parameter sniffing versus parameter sensitivity, explaining how the optimizer uses parameter values for cardinality estimation in busy OLTP systems but highlighting where things can go wrong when queries get compiled with skewed data or variable runtime conditions. Throughout the video, I use practical examples to illustrate these concepts, such as comparing execution plans and discussing local variables vs. formal parameters, all while providing a clear explanation of why parameter sensitivity is a critical issue for database administrators to understand.
Full Transcript
Erik Darling here with Darling Data. And I’m going to spend the next few videos doing the material from my past Data Community Summit 2025 regular session on parameter sniffing. And hopefully you find it as enjoyable as the millions of people who showed up on Friday morning to hear me deliver this. So we’ve, we’ve, you, you, you’ve got to see it. We’ve got that going for us, don’t we? Anyway, uh, welcome to your HR mandated parameter sensitivity training. I’m Erik Darling and blah, blah, blah, all this stuff. If you’re watching this, you’re at least moderately aware of some of this material. Um, I will try to remember to put the link to, uh, the thing in here, uh, but I make no promises. And if you don’t like it, I don’t know, leave. Um, but we’re going to start by defining some important terms here. Uh, the first one being, uh, sort of differentiating between parameter sniffing, uh, and parameter sensitivity. Uh, so parameter sniffing is when the optimizer uses values assigned to one or more formal parameters for cardinality estimation, right? And this is a thing that it, you know, I, I appreciate that it does because I work with a lot of OLTP systems that are pretty busy.
And if SQL Server were coming up with execution plans, every time one of those little tiny queries came in, um, I think things would go a whole lot less well. It’d be like, I don’t know, some, some hit every time the, the, the, like a query opened a connection and ran something. Um, if you would prefer that behavior, you can go use Postgres, right? Because there’s not really a plan cache there, right? So this is the generally okay thing that SQL Server does. Where it gets a bad name from is, uh, where it gets a bad name from is, uh, when you have a problem where a query gets compiled with some parameter or set of parameters and the cardinality estimates that produce a cached query plan for re-execution, uh, do not really fit the mold for, uh, what the current runtime set of parameters is looking for. Uh, so this is, this is what the sensitivity part is where, uh, you have skewed data or perhaps your queries are, uh, either like you have like actual physically skewed data in an index, right?
Like one value accounts for like, like a lot of rows in your table and other values account for very small numbers of rows in the table. That’s data skew. You could also expand that to be like, okay, well, you know, uh, I have a date range and sometimes, uh, or rather most of the time when I’m searching that date range, it’s like the last like hour or 24 hours or I don’t know, week or something.
And then every once in a while I have to search for the last like six months or six years of data. And all of a sudden, uh, that, that breaks the mold, right? Cause like, you know, searching for way more data within that range, uh, the query plan for searching for like this much data as usual, like those two things conflict, right?
That’s not, that’s the thing that we have to deal with. That’s the problem that we need to focus on today. There are also all sorts of general performance issues, right?
And a lot of these things can sometimes feel like parameter sensitivity, you know, sometimes, uh, you know, you deal with more data in a temporary object than others. And maybe there’s a plan reuse thing in there. Uh, there’s all sorts of other stuff that you can do that just craps all over your queries that is not parameter sniffing, but often people, you know, the, the first thing they’ll blame is like the most convenient set of words that they’ve heard about performance problems.
And like, like, I want to sound smart and be like, Oh, might be parameter sniffing. Right. So like, you know, look, SQL Server has a lot of features, right?
And, um, you know, in a database, it is real easy to shoot yourself in the foot, uh, with performance problems. And a lot of the features that SQL Server has really just make it harder to miss. Right.
So like just very good aim with some of those performance, with some of these features. So, uh, the first thing we need to do, aside from wait for this green screen to come down, I don’t know what your problem is. Sometimes if I lean away from it, it goes away.
Uh, the first thing we need to do is separate queries that are sometimes slow for other reasons. Right. Like say sometimes a query gets blocked and people are like, why is this running for 10 seconds? And it’s like, well, cause it got blocked for 10 seconds.
I mean, as soon as it, as soon as it was unblocked, it was fast. Uh, other times you might run into something that’s slow because you don’t always read data from memory. Sometimes you have to read data from disk and sometimes that is uncomfortable.
So just for example, uh, if we run a checkpoint and I drop clean buffers here and, oh, I’d must have all, must’ve had a big buffer pool on that one. Uh, sometimes, uh, you might run into a situation, make sure a query plans are turned on here. That’s a good first step.
Uh, where, you know, you run a query and you’re like, wait a minute, is this parameter sniffing? Right. Right.
Cause I am using parameterized dynamic SQL here, but did my parameter get sniffed? Was something bad? And the answer is no. Sometimes you get stuck reading pages from disk. So these two queries obviously have time differences between them.
Right. Uh, almost like 10, is that 10 X or a thousand X? I’m not, I don’t know. Let’s just say a thousand X, right? Right.
600 milliseconds versus six and a half seconds. But the whole problem is that this query had to read data from disk. If we go look at the weight stats, I don’t know how I’m going to manage this one. If you go look at the weight stats for this, this is a lot easier in person because I could, I can move things around a lot.
But the weight stats for this indicate that we spent, uh, 41 seconds reading pages from disk into memory. Right. So in this parallel plan, all eight threads had to go out to disk and read stuff.
Right. And we did not have a good time with this. But this isn’t parameter sensitivity, right? This is more like environmental sensitivity. This is more like your SQL Server can’t cache the data that it needs.
And maybe it can’t keep the buffer pool full of the stuff that it needs because you don’t have enough memory or because you don’t generally have good indexes. Or maybe you write queries in a really crappy way where you can’t like, you know, seek the stuff and you have to scan all your indexes. There are all sorts of possibilities for this.
Um, page compression is a great way to start fixing this too. But there are all sorts of reasons that might, that might be the case. But this is still not parameter sensitivity. Right.
Because that, like that query, that second query ran and it reused the same plan that the first one had. But it was a lot faster because the data was already in memory. So, um, what kind of code is parameter sensitivity?
Is parameter sensitive, right? Yeah, words. Uh, so it usually touches table with skewed data in them, right? So, like, you know, uh, I think, uh, if you’re working with the Stack Overflow database, um, you know, John Skeet would be an indicator of skewed data, right?
Uh, user ID 22656. Uh, lots of rows in the post table. Lots of rows in the comments table.
Um, and so his user ID leads to a lot of interesting problems. You can also run into it when, uh, you know, like not with the, like with things that are like inequality predicates where sometimes they may find way more data than others. Right?
So, like, you know, it’s really easy to imagine that with a cache plan using something like this. But you can also run into that with like predicates. You can run into that with in and not in stuff. So there are lots of ways, lots of places you can run into skewed data.
Now, I think like is a particularly fun one. We’ll look at an example with that in a few. But other things about it is you will be using formal parameters and not local variables. Right?
Local variables do not have parameter sensitivity issues because their cardinality estimation. is treated much differently from that of formal parameters, literal values, and other such things. Um, we’re not too concerned with the concept of simple parameterization here.
Because it’s really only used for queries that qualify for what’s called a trivial plan. And trivial plans do not really have any cost-based choices. Having cost-based choices to make is where the parameter sensitivity stuff really kicks in.
Because SQL Server made choices based on cost from one example of cardinality estimation for a set of parameter values. And then reusing the plan for a different set of parameter values where the costing would maybe work out differently because of the number of rows involved. That’s where the sensitivity end comes in.
So if we look at execution plans for these two queries, right? We’re just going to get a count from the user’s table. One of them is for reputation two and one of them is for reputation one. I have literal values in my query, in my, both of these queries, right?
But if we run this and we look at the plans, then of course we have to move some stuff around. And actually, no, we can just use the arrows, I guess. Thanks, SSMS.
Thanks, SSMS. Making me do more work. The last time I upgrade you, you fiend. So if we look at these, you see that SQL Server did, like, replace both of my literal values with this at one parameter. And that means that SQL Server gave a, like, was like, hey, this plan seems pretty trivial to me.
And then SQL Server also said, and I think I can apply simple parameterization here. And so it did. And so we end up with this.
And we can also see some artifacts of that because SQL Server did cardinality estimation for reputation equals two. And I guess it was pretty good. It was a pretty good guess, right?
We were only off by, like, 190-something rows or, I don’t know. Doing math on the fly is not my specialty. So this was pretty close, right? But then down here, you see SQL Server reused that same cardinality estimate of 1770.
So SQL Server parameterized, cached, and reused my plan for these two queries with literal values in them. So using literal values does not always insulate you from these types of things. But often the queries that get this are so simple that we’re not really worried about, you know, big performance problems with them.
But anyway, the two most common vehicles for parameterized code in SQL Server are, of course, store procedures and queries that are executed from an application using SP Execute SQL. SP Execute SQL is a store procedure. And when you execute dynamic SQL with it that contains parameters, you are essentially executing parameterized code.
All right. But we’re going to talk a little bit about the main distinction between formal parameters and local variables here. Because sometimes the local variable thing matters and sometimes it doesn’t.
I’m going to start these two queries off running. And then we’re going to talk a little bit about that. Oops, that was supposed to. Oh, that was two queries. Sort of jumped around on me.
So the thing with local variables is, and, you know, I’ve talked a lot about this and written a lot about this. But the main thing with local variables is, like, it’s very easy to come up with a demo where there’s one thing in it that’s a local variable and that local variable screws everything up. In real life, often the times when I see code that uses local variables, the local variables aren’t the only thing in there.
Right. Right. So when you look at, so, like, if you’re using local variables, sometimes it’d be, like, other things in the where clause, like other, you know, search arguments in the where clause that might sort of tame the effect of bad cardinality estimation from local variables. I’m not saying that it’s okay to use them all the time.
But I am saying that a lot of the times, you know, you might have other stuff in the code that sort of hides or, like, sort of, like, smudges over a little bit about how bad those estimates can be. And other times you’re looking for, you know, such small amounts of data that it doesn’t matter. This is an example of looking for when it can matter and when it actually doesn’t matter.
So the first time that we run this, we’re looking for, we have a local variable where parent ID is zero. And the second time that we run this, we’re looking for things where parent ID equals 184618. So one query obviously takes far longer than the other.
This one takes 10 seconds. And this one down here takes one millisecond. The reason why all comes down to the cardinality estimates and the number of rows that we actually found. You’ll notice that both of the index seeks into our index on parent ID estimate three rows are going to come out of it.
That’s the local variable density vector guess. Of course, in the first query, we actually find six million rows. And so this was not a good situation.
If you find yourself running into situations like this and saying, SQL Server came through cardinality estimation, well, that’s your fault. But if you run into situations like this where you’re like, oh, SQL Server guessed three rows, but we got 518 rows and everything turned out okay. I don’t know.
Maybe you’re safe for now. But don’t say I didn’t warn you. Local variables can really screw up query performance stuff, especially as things get bigger and bigger. But if we look at the data distribution for stuff in the post table, we’ll see that just because the post table is not well normalized and we have questions and answers living together, we have six million rows for parent ID of zero, which are questions.
And then we have just the top nine or so posts and the number of answers they have. So non-zero parent IDs are answers. The one that has the most rows in it is 518.
So this is where like, you know, like these very low numbers is where SQL Server starts to get these like these estimates for local variables from. But let’s talk about some other stuff. If you want to read in detail about local variables, I have a post there.
It’s a long post, but, you know, it’s a good one. So it’s sort of interesting, like why that happens in SQL Server. And this is sort of a thing.
Well, I mean, it used to be table variables would get sort of a similar treatment, right? You would run code that involved a table variable. And unless you had a recompile hint on there, SQL Server would always guess one row was going to come out of the table variable, even if you had an index on it, because SQL Server doesn’t maintain any distribution statistics about data that is in table variables, right?
So Microsoft changed that sort of recently. In SQL Server 2019, we got this table variable deferred compilation thing, which is kind of a smudgy name, but it really means that when you have code that uses a table variable, SQL Server will defer compilation of queries that touch that table variable until after the table variable gets populated.
So we know how many rows. We have table level cardinality for what goes in there that, of course, doesn’t extend to any like, you know, column level statistical histograms of data in there. But we at least get table cardinality.
So Microsoft could technically do this with local variables as well. It could say, I’m going to defer compilation of any queries that touch these local variables until that local variable has a value assigned to it. Right now, we don’t do that.
Right now, SQL Server says, I’m screw it. I’m going to compile a plan. And in order to do that fast, I’m just going to use these unknown estimates for any local variables in my where clause. If we did this in the future, well, if we did, we can change that.
Right. And if we did the deferred compilation thing where we didn’t compile a plan for any queries that use those local variables and like the where clause. If we did it every time, it would just feel a lot like you’re just recompiling, like just constantly.
If we just do it once, let’s say for a store procedure, and then we just use that plan over and over again, it would just feel like parameter sniffing all over again. And sadly, a lot of people use the local variable thing to get around parameter sniffing. So the internals to do the second thing are actually in the product.
They’re just not available to us. From what I hear, you need window bug and you need to start altering memory addresses in order to expose it. It’s not even like some super secret trace flag, but you could get local variables to act the way that tape, to act with local variable deferred compilation.
I guess it would be if you’re feeling bold and brave and doing it. But like I said, since people historically have used local variables to avoid that, the powers that be have decided not to break this behavior for them. You know, this, of course, could be introduced with some sort of database scope configuration or, you know, query hint or something.
But, you know, we just don’t have access to that yet. So let’s move on because what we have to talk about, at least probably the last thing in this video that we’re going to talk about, is recompile. So I love recompile.
I think it’s a fantastic thing. I think it’s a fantastic technique to use. You know, there are, of course, some caveats to that around, you know, like planned compilation time and execution frequency. But, you know, for like reporting type queries, I don’t see a reason to ever not have a recompile.
You would have to convince me to not put a recompile hint on a reporting query more than you would have to convince me to put one on. But there’s one thing that people continuously screw up. And that thing is where to recompile.
Because there is a difference between recompiling at the procedure level and recompiling at the statement level. So let’s create this index. And then let’s look at this store procedure here.
So this store procedure does have the procedure level recompile up at the top. And I say this sucks because this is really not my preferred method of recompiling. This recompile hint here is, this doesn’t have the same magic powers that it does if you put a recompile hint down lower.
So this actually has the same query repeated twice in it. There’s this one here, which is just a normal query. And then there’s this one here, which has option recompile at the end or a statement level recompile.
If we run this store procedure, which is going to execute both queries, right? It’s not one or the other. This doesn’t go terribly.
But there are some obvious differences between the two execution plans. The first one up here goes parallel and does an index scan. Okay. Second one down here does not go parallel and does an index seek.
This is the one with the statement level recompile on it. If we scroll on down a little bit, you’ll see the option recompile in here. But so the main difference between the procedure level recompile and statement level recompile is that statement level recompile gets you what’s called the parameter embedding optimization.
And you can see that if you hover over the index scan and you look at the predicate that gets applied up here. SQL Server doesn’t seek with this. SQL Server scans the whole index and tries to figure out if various things are null or equal to columns or whatever.
Right. There’s just a lot of stuff going on in here. In the second one, SQL Server simplifies that whole thing and just seeks to where owner user ID equals 22656. Performance isn’t dismal in either of these cases.
But if you’re going to ask me if I’m going to pay the price to recompile something, which one I would prefer? It’s obviously the second one. All right.
Obviously, the statement level recompile gets us a little bit closer to what we like, you know, reality on this. So if we run this with using a different parameter this time, right? Because remember, the pattern in here is this whole wacky, nutty thing, right?
This, you know, the old kitchen sink store procedure type pattern. If we run this with creation date now, SQL Server is going to give us a different execution plan for both of them. Right.
Because we have both a procedure level recompile, which means SQL Server is going to generate a new execution plan for all of the queries in this procedure when it runs. And then we have a statement level recompile, which tells SQL Server, give me a new query plan for this one query every time it runs. So what happens is SQL Server, when we use a different parameter, SQL Server does not reuse the plan from before.
SQL Server gives us a new plan for both of the queries in there. But this one, we still have this whole big predicate. SQL Server tries to figure out all of the knownness or not knownness of all of these parameters.
And in the second one, SQL Server simplifies it to one predicate that just says greater than or equal to 2013, 12, 31, 0, 0, 0, 0, 0, 0, 0. So my strong, strong preference is to use the statement level recompile and to not maybe generate new execution plans for absolutely everything in the procedure and target particular queries within the procedure that might be parameter sensitive or might be using local variables, which can also benefit from the parameter embedding optimization. SQL Server 2025 also has a new feature to try and deal with the optional parameter thing called OPPO or the optional parameter plan optimization.
It’s built closely around the same architecture as the parameter sensitive plan optimization. I have not had great experiences with that yet. The OPPO one.
And we’re going to talk more about the PSPO one a little bit later. The first thing that I want to show you, though, before we, the last thing that I want to show you before we go and before I start talking about something else is the effect that using option recompile can have on queries in the plan cache and in query store. So if we look at this first query up here, and I think if we open both of these, we should be in good shape.
If we look at both of these, right, we’ll have, these are two of the queries that ran from the procedure that we just, sorry, these are the two queries that ran from the store procedure that had option recompile hints on them. If we look over here, or rather if we start, let’s start over here because maybe I should do both. There is no parameter list for this, even though this came from the store procedure.
And even we, even though we know that SQL Server like ran a parameterized query because we have the option recompile hint on here. We have to go find the literal value in here. Right.
Right. And that’s the same thing is true for both of these. If we get rid of these and we look at this without the recompile hint, and I’m going to, I know I’m going to regret doing this because I’m going to have four of these and I’m going to have to figure out which ones are which. So what I should do is go look at the query IDs.
So that’s two, six, seven, eight, two. All right. So let’s get rid of this. So avoid two, six, seven, eight, two. We want two, six, seven, eight, one. One, if we look at the query plan for just this first one and we go to the properties, SQL Server will, this is the one that used the procedure level recompile.
This one we can see the parameter list in, but we’ll just see which ones were null and which ones actually had a value to them. So this is the one that executed where we use creation date. So we have the parameter compiled value in here, but we see the parameter compiled value for the rest of these is all null.
So that was way longer. Well, not way longer. That was just a little bit longer than I expected it to be.
But we’re going to end here and we’re going to pick up next looking at a minor parameter sensitivity example. So we’ve got that to look forward to. So thank you for watching and I’ll see you over in part two of this.
All right. Goodbye.
Going Further
If this is the kind of SQL Server stuff you love learning about, you’ll love my training. Blog readers get 25% off the Everything Bundle — over 100 hours of performance tuning content. Need hands-on help? I offer consulting engagements from targeted investigations to ongoing retainers. Want a quick sanity check before committing to a full engagement? Schedule a call — no commitment required.