Diagnosing and Fixing tempdb Contention from Spills in SQL Server

Diagnosing and Fixing tempdb Contention from Spills in SQL Server



Thanks for watching!

Video Summary

In this video, I delve into the fascinating world of TempDB contention caused by spills in SQL Server. Joining forces with my buddy Erik Darling from Darling Data, we explore how these spills can significantly impact performance and cause TempDB to become a bottleneck. We demonstrate this through an engaging example using SQL Server Management Studio and SQL Query Stress, revealing the ins and outs of how spills affect query execution times and CPU usage. By the end of the video, you’ll understand the importance of indexing and sort order alignment to mitigate these issues, ensuring smoother database performance in highly concurrent environments.

Full Transcript

Your best friend in the world, Erik Darling here with Darling Data. And in today’s video, we are going to talk about how spills can cause TempDB contention. Isn’t that just miraculous? We can just keep finding new ways to abuse TempDB. If it wasn’t like table variables and temp tables and, you know, I don’t know, all the other stuff that hits TempDB, if that wasn’t enough, now we have to worry about spills causing TempDB contention. And, boy, does that suck. But first, let’s talk about you and me. If you have already done this stuff, if you have already liked, if you have already commented, if you have already subscribed, and I do thank you from the deep, deep, dark bottom of my heart for doing that, and you want to go steady with me, you want to take things to the next level, you can click the link in the video description just below to sign up for a low-cost $4 a month membership. To the channel, which will just say thank you for doing a good job. That would be cool. If you need help with your SQL Server, and you think, boy, Erik Darling sure would be useful doing any of these things, hit me up. My rates are reasonable.

If you need me to do anything else with SQL Server, the same thing applies there. We can talk. My rates will remain reasonable just for you. Just make sure that you said, make sure when you hit me up, you say, hey, I’m coming from your YouTube channel where you said your rates are reasonable. Otherwise, I’ll have no idea who you are, and I might say something unreasonable. If you would like to get some high-quality, low-cost SQL Server training that is good for the rest of your life, so the longer you live, the more it’s worth, you can get about 24 hours of it for about $150 at my site there.

You can use the discount code SPRINGCLEANING for that. If you want to catch me live and in person, I will be in Seattle, Washington with Kendra Little doing two days of SQL Server performance tuning witchcraft, wizardry, warlockery. And it would be cool to see you there because it’s my birthday and I’m making goodie bags, so you have that to look forward to.

With that out of the way, let us begin our SQL Server partying. Now, let’s go over to SQL Server Management Studio and let’s make sure we have no indexes here. The first thing I want to point out is that if we run this store procedure on its own, it’ll run for about 1.3 seconds, right?

We spend about 200 milliseconds scanning the clustered index and then another second or so over here in the sort, right? Okay, so this runs pretty quick right now by itself with nothing else going on. Let’s come over to SQL Query Stress and let’s run this.

And we’re going to give this a few seconds to warm up. What we’re going to see while this starts warming up and doing stuff is that the queries start running for longer and longer over here, right? No longer are we at just the 1.3 second mark.

Some of these have been going for almost 4.5 seconds. What you’re going to see over in this column is a lot of stuff being runable, right? So runable is going to be SOS scheduler yield related.

And if we keep looking over here and running this, all of a sudden we’re going to start seeing queries taking a lot longer over here. Now, classic tempdb contention signs would be seeing stuff like in the wait info column like pagelatch underscore UP or pagelatch underscore EX. And sometimes you might see that in here if you have enough of the spill contention going on.

That could still totally happen. But we’re not seeing that in there. What we’re seeing are queries running longer and longer because they’re getting bogged down in tempdb all trying to do the CPU work to deal with the spills. Now, keep in mind, this is only 50 threads, right?

It’s 100 iterations, but it’s only 50 active threads at a time, right? All the results from spwhoisactive will say about 50 rows down here in the armpit zone for anything that’s actively running. So, like, not a lot in here is, like, you know, like, I’m not exhausting worker threads as part of this.

If I ran a lot more threads, things would get a lot worse. But you can see in this column that now stuff is taking, like, 9, 10, 11 seconds in here. And, indeed, if I run my store procedure with the rest of the workload going, it’s going to take a while, right?

That was, like, 1.3 seconds before. Now this thing took, well, just about 10 seconds. Or, sorry, just about 9 seconds.

8.675 seconds now here. 726 seconds here. If we look at the weights for this query, right? If we come over here and look, the weight stats for IO completion won’t be that bad, right? This is the typical sort-spill thing in here.

And I think that there’s been some improvement in the row mode algorithm for that. Really, what we get bogged down in is the SOS scheduler yield weights over here. There was about 6 seconds of those for the 8-second query.

So, about 6 of the 8 seconds that we spent waiting for this thing to finish were just, like, waiting on, like, CPU, cooperative CPU scheduling. And, again, this isn’t a ton of stuff going at once, right? I’m not, like, you know, throttling the server with 1,000 active requests.

This is just 50 queries running. Granted, they’re all doing the same thing and they’re all spilling. But, you know, that’s just kind of funny to see, right? So, yeah, it’s like running this over and over again, things get kind of wonky.

And we, so, this ran for almost three minutes and we barely completed, a little over 900 total copies of this query finished in that time, right? So, like, on average, about five and a half seconds of CPU. The, like, some of the iteration average in there, 17 seconds.

So, things were getting really slow on the server itself. Now, with nothing running on here, if I come back and run this again, tempDB goes back to normal. We’re at about 1.3 seconds for this.

Cool. Let’s look at how we might fix that. Now, in-memory tempDB stuff might help, right? Depending on what kind of contention you’re seeing.

If we were seeing the page latch contention, then in-memory tempDB stuff might be good. You know, but I think, you know, for a lot of the queries where I see, you know, something like this where, you know, there’s a little spill from a little sort and we could fix it pretty easy with an index, we should do that. Now, one thing that might surprise you a little bit is that creating this index on reputation does not fix the spill.

Or, rather, does not get rid of the sort. It does make the sort smaller, right? Or, it does make the sort faster because we’re reading from a much smaller index.

But, it doesn’t actually get rid of the sort. To do that, we need to actually match the sort order of the reputation column here in the index to the sort order of the query. Now, a lot of people might write stuff like, does index sort direction ever matter?

No, it’s stupid. Leave it alone. You’re an idiot. Leave it. That’s not true. There are lots of times when it does matter. This can be one of them.

Another time when it matters quite a bit is when you’re writing windowing functions. And, the windowing function specifies, I mean, not in the partitioning clause because the partitioning clause doesn’t have an ascending, descending. But, in the order by clause of a windowing function, you might specify some column descending.

And, then, your index sort direction matters quite a bit. Okay. So, with this index created, at least I’m pretty sure it’s created, if we run this, we’ll see that this query finishes just about instantly. And, now we have a top here but without the sort involved.

So, now, if we come over here and we run SQL query stress for, again, 100 iterations on 50 threads, we won’t even have time to get back to SQL Server management studio because this will have completed in about 15 milliseconds. So, that’s a pretty good deal there. I like it quite a bit.

And, I think you should, too. So, anyway. With that out of the way, thank you for watching. I hope you enjoyed yourselves.

I hope you learned something. I hope that you are now able to go out into the world and resolve TempTB contention issues from spills because it’s a good thing to do. You’ll probably be a hero to someone if you start fixing these sort of problems.

Now, granted, these aren’t the sort of, like, big query problems that you might see from, like, reports where you go from, you know, 30 minutes to 3 seconds or something. But, this is the kind of, like, you know, highly concurrent workload stuff that you have to start thinking about and analyzing and addressing when you’re dealing with big, highly concurrent OLTP workloads because this kind of stuff can really sneak up and bite you. So, you know, as usual, spwhoisactive is a great tool to start tracking this stuff down.

And, you know, the more you can do to, you know, monitor and get an idea of what’s going wrong on servers when things are in a highly concurrent state, the better job you can do of starting to tune things so that you don’t have to worry about the server falling over. If I were to, if I really wanted to make things awful, I could have thrown way more worker threads at this and things would have been just terrible in here, right? 50 active users, right, obviously doesn’t take much to bog down a SQL Server.

So, you know, you can see some really profound effects even from small amounts of really highly concurrent spilling activity. So, anyway, that’s about it for this one. I got a couple more videos about rewriting functions and function inlining and stuff.

So, we’re going to get to those. And then, I don’t know, I think we’re going to, I don’t know what we’re going to do, to be honest. We’re just going to close these tabs out and I’m going to get on an airplane tomorrow.

Then I’ll figure out what to do when I get home. All right. Thank you for watching.

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.

More Annoyances With Local Variables And Optimize For Unknown Hints In SQL Server

More Annoyances With Local Variables And Optimize For Unknown Hints In SQL Server



Thanks for watching!

Video Summary

In this video, I delve into the frustrations and challenges that local variables and the `optimize for unknown` hint can bring to SQL Server performance tuning. While these techniques are often touted as best practices, they can significantly complicate the process of diagnosing and resolving performance issues, especially when trying to reproduce them in a testing environment or during code reviews. I demonstrate how using local variables and `optimize for unknown` hints can obscure crucial information from execution plans, making it nearly impossible to pinpoint the exact conditions that led to poor query performance. By highlighting these annoyances, I hope to encourage database professionals to reconsider their use of these practices in favor of more transparent and maintainable alternatives.

Full Transcript

Erik Darling here, with Darling Data. Don’t you just love it? Don’t you just love every minute of it? I sure do. Except the odd-numbered minutes, and some of the even-numbered minutes. A few of the seconds are great, though. Milliseconds really stand out. In today’s video, I’m going to talk about some annoyances that I have with local variables and optimize for unknown hints. Now, you’re probably used to hearing me complain about these from the problems that they cause with cardinality estimation, not using the smart part of the histogram to figure out how many rows might qualify for a WHERE clause. We’re going to look at something a little bit different in this video, because it’s something that I run into quite a bit when I’m trying to help clients with things, and it makes it really, really difficult to figure out Well, if I say it here, it’ll give away the whole video. So, I’m going to wait and say it. Let’s just say I’m sufficiently annoyed with local variables and unknown hints that we need to talk about them from a completely different angle this time around.

But first, let’s talk a little bit about money. Like, if you want to give me $4 a month, you totally can. There’s a link in the video description where you can join the channel with a membership subscription thing, and it’s cheap and it’s worth it, because you get a lot of free content out of it. If $4 is beyond your means, maybe your rates are a little too reasonable, and you can’t afford $4. That’s totally cool. I like likes, I like comments, and I like new subscribers. I like new faces showing up in the comments saying, Hey Eric, how you doing? It’s about all it takes to make me happy. All the other stuff just, you know, buys me wine, I guess.

If you need help with your SQL Server, if something that I talk about in one of these videos strikes you as something that you think that I would be particularly valuable to assist you with, any of these things, my rates are reasonable. If you want some high quality, low cost training, for life, forever, you can get about 24 hours of it for about $150 US with the discount code that’s also in the video description.

So the more things you click on there, the better off we all are, right? In reality, just clicking on the links in the description, and it will improve all of our lives greatly. If you would like to see me live and in person, you can do that. November 4th and 5th in Seattle, Washington. I don’t know if there are any other Seattles. I’m unaware of them. But I’ll be there for my birthday.

And I’ll be hanging out with Kendra Little for two full days of performance tuning magic and wonder. It’s going to be like you were a kid again, right? Watching someone do like one of those finger tricks where they, ah, where’d it go? I wasn’t flipping anyone off, I promise. I can’t blur this later, so screw it.

Anyway, if there’s an event near you that you think I would make a good speaker at, a good addition to the lineup, let me know what it is. If they’re accepting pre-cons and I can, you know, cover the cost of travel to get there, I’ll happily show up and teach people live and in person. That’s my favorite. So with that out of the way, let us begin our festivities. Let us start the party.

We will commence with the fun. So let’s go over to Management Studio. And I’ve got a store procedure here, just to make sure that we are thorough in our investigation.

I’ve got a store procedure here that does three things. It’s got one query that just takes some regular old parameters right there, right? That’s just the normal parameter way of doing things.

And then I have a second query where I declare a couple variables and I use those instead of the parameters here. And then I’ve got a third query where I use the variables, but I add this optimize for unknown hint at the very end. Now what I’m going to do is I’m going to run each of these.

And when I run each of these, we’re going to get results back, you know, fairly quickly. This doesn’t have to be like miraculously fast. What I want to show you here is really not really the cardinality estimation thing.

Like we can see the cardinality estimation thing here where like, you know, for the first set of compiled parameters, we got a good estimate and everything was cool, right? Well, I mean, really up here is where it matters where we seek into the index where the thing is and where it falls off is when we look down here, we’ll be at, oh, there’s only 123 rows. And that’s the same for both of these.

And that gets, you know, I don’t know, it becomes a thing for the other queries where, you know, the second time we compile with the actual parameters, we get the cardinality estimate from the first one. And that also is true of, oh, stop moving. Gosh, darn it.

You silly thing. That’s also true of the two optimized for unknown things where we get the, you know, the actual number of rows, but of the much worse guess, right? So this guess just is stupid for everyone.

It doesn’t make any sense. But, you know, the parameter sensitivity aspect of the first one is why a lot of people end up doing the up, the, the local variables or the optimized for unknown thing. I’ve had a number of people tell me that they thought it was a best practice in SQL Server, at which point I feel like this hand get real slappy.

Not, not in a way that would gratify anyone. So what really bothers me with this stuff, though, is just to clear things out a little bit so I don’t have six plans staring at me. What really annoys me is when you run a store procedure like this one and you get the actual execution plans, what you’ll see if you go to the properties of this.

Now, let’s say you’re in a situation like me, where you’re a young, handsome consultant and someone has hired you to help them tune their queries and their store procedures and all that other good stuff. And you’re trying to help like trying, you’re trying to reproduce a performance problem with a store procedure. If you use actual parameters in your store procedure, but what you’ll end up with over here are the compile and runtime values for both of these.

Right. So there’s the parameter compile value. There’s a parameter runtime value. We see that repeat for both of the parameters that were passed in. So if I wanted to, I could very easily take these out and I could take the compile values out, run the procedure and see if it’s slow, see if we can make any improvements. Where that changes a bit and I’m going to highlight this so it doesn’t disappear on us.

Where that changes a bit is if we look at the local variable version of this, some things start to disappear on us. All of a sudden, all we have are the runtime values. Now, if you have, if you’re looking at it, if you’re able to execute a store procedure, the runtime value should be obvious to you because you just put them in.

You just told SQL Server what to do there. Right. And that’s going to be the same for down here as well, where all of a sudden all we have is, well, the runtime value. Right. Just zero and two there. So that’s OK.

But this isn’t really my problem. My problem is if we look in query store or the plan cache and we’re like, wow, that store procedure sure was slow. We should probably do something about it. If we look at what happened in there, I’m going to show you two things. Actually, I’m going to show you all three things. The first query up top should be the optimized for unknown hint.

And if we look at the query plan for this and we say, well, this was the slowest thing in there. Let’s see what we passed in there. And let’s try to figure out how we can reproduce this performance issue so we can start fixing it. Well, we don’t have a parameter list over here.

Right. For, you know, for a little bit of what do you call it? A little bit of contrast on that. Let’s look at the query plan for the third one, which has nothing on it.

If we look at the query plan for the third one, we have a parameter list here. And we can see what the compile time values were. Right. So we can get the compile values for both of these parameters.

And we could plug these into the store procedure and we could say, oh, this is why you are slow. We can fix that. But optimize for unknown and the local variables strip that stuff out.

They completely hide that information from you. So you have absolutely no idea what happened in there. This is the up. This is the one with the local variables.

And we don’t have that parameter piece in here so that we can reproduce this. Now, on the one hand, I think this is partially Microsoft’s fault. Even though that even though these are local variables and optimize for unknown hints, you still know what the what the values of the of the parameters they were compiled with were, even if those parameters didn’t really come into play for cardinality estimation.

Remember, when you use local variables of the optimize for unknown hint, SQL Server looks at the different part of the histogram that tells it how unique it thinks the column is that you’re looking at and how many rows are in the table. And it multiplies those together.

So even though the values weren’t used for cardinality estimation, the compile value should still be in the query plan because they might be useful. Amazing. I know. Right. Why like why wouldn’t they be there?

But this is just another crappy side effect of people following bad advice or people following awful, like, non-existent advice that optimize for unknown and using local variables to substitute parameters in is a best practice. You end up in situations where it’s impossible to figure to reproduce anything to pull it out.

And then you’re then you’re stuck trying to figure out like, OK, is there application logging for what people did? Like what are common values for things? For me, with the Stack Overflow database, it’s very easy to like look at the reputation column and look at the uploads column and just come up with a couple values.

In real life, if you’re trying to find like customer IDs and order IDs and dates and all sorts of other things that could really make or break you being able to reproduce a performance issue, it really screws you badly. So please, please, please, please stop doing this. You know, recompile hints are almost the opposite because recompile hints will cache the actual values, but not like in the parameter section.

You have to like look at where you have to like look at where you touch the index to see what actual values are applied in there. So recompile has its own sort of issues. But typically, once something has a recompile hint on it, it’s usually fast anyway.

So I’m kidding. Recompile doesn’t fix everything. It just fixes crap like this. So that’s that’s nice, too.

Yeah. Anyway, please stop using local variables as parameter substitutes. And please stop using optimize for unknown hints because they make my job harder. My job’s already hard.

They have to fix your code, your indexes and your databases on your servers. It’s not easy, but it’s OK. My rates are reasonable.

All right. Thank you for watching.

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.

A Little About FORCESEEK Hints In SQL Server

A Little About FORCESEEK Hints In SQL Server



Thanks for watching@

Video Summary

In this video, I delve into the world of tuning queries that use OR clauses in joins, specifically focusing on a scenario where SQL Server’s natural query plan choice is far from optimal. I explore why SQL Server might choose such a suboptimal plan and demonstrate how forcing index seeks can significantly improve performance. By walking through detailed examples and explaining the reasoning behind each step, I aim to provide practical insights into when and how to use hints effectively in your queries. This video is part of my ongoing series on query optimization and performance tuning, designed to help you navigate the complexities of SQL Server and ensure that your database operations run as smoothly as possible.

Full Transcript

Erik Darling here with Darling Data. In case you’re wondering why I am recording at night, well, it is because it is Wednesday, September the 4th? 4th. 4th. And tomorrow morning I fly out for Data Saturday, Dallas, because I have to be there to teach a pre-con and stuff. And I just want to make sure that I have sort of a clean slate of stuff when I come back from that, because I’ve just got a few videos that I want to get nailed down before I leave, both to make sure that I have the blog queue and the video queue amply done, because whenever I’m gone for a few days and I don’t have time to record and do stuff, I need to make sure that my fiduciary duty to you is complete. And tonight’s video, I think this is the 10,000th video I’ve recorded tonight, we’re going to be talking about tuning or clause joins.

And the reason why we’re going to do that is because I have a few videos about joins with or clauses in them, and some of the pain they cause, and some different ways of approaching them. But this one’s a little bit different, because in this one, rather than doing this sort of, what I would consider the Darling Data standard rewrite, where I do a union all of things and work off that rather than working off of, and really doing anything else, it’s pretty much just that. We’re going to look at things in a slightly different way.

The slightly different way is going to assume that we have pretty good indexes in place, because without them, using what we’re going to use in this video is going to be pretty fruitless. But before we do that, as usual, if you like this video, if you like this channel, or this video, or whatever enough to start a membership, you can do that for the low, low cost of $4 a month. Absent $4 a month, you can contribute other parts of your body and soul and mind by liking, commenting, and subscribing. All very noble things to do.

If you need help with SQL Server in any of these ways, or I don’t know, maybe any other way, just don’t call me about like a problem with replication. My rates are reasonable. If you would like some high quality, low cost SQL Server training that can take you from your obvious beginner level to your next intermediate level, and then to expert level and beyond, you can get all 24 or so hours of mine for about $150 for your entire life. Or until I stop paying the bills, because no one gets a membership to the channel. We’ll see what happens.

But I’m kidding. It’s prepaid for like a decade or something. And in 10 years, if you still haven’t watched them, I’m not the one who’s messing up there. That’s you. So, yeah, there’s that.

And of course, this November 4th and 5th, I will be at Past Data Summit with the amazing Kendra Little. And we will be tag teaming two days of performance tuning. What’s a good T word? I don’t have one off the top of my head.

Performance tuning toughies. That didn’t go well. And of course, if there is an event near you where you would like to see me live and in person, tell me what it is so that I can submit to it.

Because if they choose me to do a pre-con, I will probably show up relatively sober. With that out of the way, let’s begin our voyage into performance tuning these ridiculous queries. Now, the reason why I care about this stuff is because these queries are notoriously slow on their own.

This is a join with an OR clause where I have no hints in this query. And SQL Server will every single time naturally choose the most god-awful query plan. And I’m going to break this down a little bit, but not this one, because this one only runs for about 30 seconds.

So, like, depending on the size of the tables, like, this is joining users to posts. The users table is fairly small. So nothing too, too awful happens here.

30 seconds is, you know… Oh gosh, that’s a slow query. I mean, it does just about reach the, like, usual application timeout threshold of 30 seconds. So, you know, it has that going for it.

I couldn’t put that in its dating profile. But the one that I would much rather focus on is down here a touch. And this is where I have joined… I don’t know why you decided to refocus, Management Studio.

This is where I have joined comments to posts. And these are two much larger tables. And things get much, much worse here. All right.

So what I want to point out a little bit about this query plan pattern that I find so noxious is… Look at… We do, like, what I would consider one too many joins.

All right. Not that I have, like, a number of joins in my head where I’m like, Oh, there’s too many now. It’s more that, like, for the particular query that we wrote, There’s a stupid join in here that we just shouldn’t have.

Right? Because we have a nested loops join here that goes to another nested loops join here that goes to the comments table. Okay.

So how did we get from… Oh, sorry. Oh, over here, where I can’t quite reach with my arm because of screen limitations, all the way down here to a seek into the comments table that took six minutes? Good question.

Well, we started by taking all 17 million rows from the post table. Right? That’s this number over here. 17, 1, 4, 2, 2, 0, 0.

17 plus some. And we took all 17 million of those rows and we broke them up into two parts. There’s a constant scan here with 17 million rows and there’s a constant scan here with 17 million rows.

And each of these constant scans represents the… What I’m going to say is the different join criteria that came out of that. So if we look at the query that got written, it’s going to be this one.

So it’s where the p.ownerUserId equals the userId in the comments table or the last editorUserId equals the userId in the comments table. Okay?

So if either one of those columns from the post table matches that one column from the comments table, we need to figure that out. So one constant scan is all of the ownerUserIds. The other constant scan is all of the last editorUserIds.

All right? So coming back over to the plan, you will see 17 million rows come out of this one. And 17 million rows come out of this one. And SQL Server slaps them all together.

So we have 17 million times two right here. And then SQL Server decides to spend 20… Let’s just say about 20 seconds sorting.

Both of those inputs. Why did it spend 20 seconds sorting all of those inputs? Well, because it was trying to remove some rows from those inputs.

So we start out with 3, 4, 2, 8, 4, 3, 3, 8. That is an 8-digit number of rows. And we end up with 3, 0, 7, 6, 6, 4, 2, 9.

That is still an 8-digit number of rows. Granted, we got from an 8-digit number that started with 3, 4 to an 8-digit number that started with 3, 0. But I don’t know if that was quite worth the now about 25 seconds of time that we spent doing that.

And, you know, perhaps, right? Because, you know, that would have an impact on this, right? We spend 8 minutes.

So, like, this goes from 25 seconds to 8 minutes. And we know we spent 6 of those minutes seeking into the comments table down here. Right?

So all of that nested loops join, that’s an 8-digit number of nested loops. That’s 3, 0, 7, 6, 6, blah, blah, blah, blah, blah. And if we look at this thing over here, and we look at the number of rows that ended up per thread, that is just a ghastly amount of work.

In this case, the parallel nested loops join was a pretty rough trick on us. So in all, this whole query, once we finish, you know, with this other nested loops join, we add about another 2 minutes on there.

And we end up taking almost 11 minutes to complete this whole query. The reason why this is so absolutely frustrating is because SQL Server could naturally choose a much better plan, but it just doesn’t.

So one thing that we can always try, assuming that we have adequate indexes in here, and by adequate indexes, I mean stuff like around this makes about sense, where we have the, you know, the tables and columns that we’re joining on all properly indexed for stuff.

We can run these two queries. And the only difference between these two queries and the two that I ran up front, where I stuck a force secant on the post table here and on the post table here.

So remember that first query took about 30 seconds and the second query took almost 11 minutes. But just throwing a force secant on these two queries, you know, that does, does the Lord’s work. By the Lord’s work, I mean makes them faster.

By the Lord, I mean me. I’m the Lord of SQL. I suppose that involves some dancing. So for the first query where, again, this is a plan shape that SQL Server could have completely validly chose. Right.

But it just didn’t. Something actually, you know, I’m kind of interested in doing. Kind of, kind of want to see what’s, if something that I forgot to look at actually was the costing. Right.

So let’s look at the cost of this 30 second query was, oh, why did you go away tooltip? Uh, 2883.55 and the 30 second query was 28. So just around 2800 query bucks a pop.

If we come look at these, these plans, look at the estimated subtree cost of this. 30,000. Right. Look at the estimated subtree cost of this. 11,000.

So this is where things get really annoying. SQL Server think, like costed these two plans out of existence. Even though the first query was 30 seconds and we got that down to four seconds with a query that cost 30,000 query bucks. And then we got this from 10 minutes down to four seconds with a query plan that cost 11,000 query bucks.

But SQL Server won’t ever choose those plans naturally. SQL Server, just the costing for this thing sucks. Right.

And this is why I spend a lot of time either applying for sequence or rewriting queries with the union all technique that I’ve described in other videos to make these queries faster. Right. So just throwing a force seek on it on this saves.

Oh, I don’t know. Uh, let’s just call it about 10 minutes and 38 seconds here. Right. So that’s, that’s pretty good.

And, uh, it, you know, saves about 25 seconds here. So don’t be afraid to play with hints when you’re tuning queries. If a query plan looks stupid to you, ask SQL Server why it didn’t do that.

That’s what the point of these hints are. Uh, if you, especially if you have queries that are joining with an or clause and you, you know, you have good indexes in place. Stick a force seek hint on one of those tables and see where it gets you.

A lot of the time you will get a much, much better plan than SQL Server is willing to come up with naturally. Um, again, it’s the costing mechanism behind this, which is so totally boned that you like, you really have to like, uh, you really have to intervene here. The other thing that I want to bring up with this is that, you know, something that I’ve, I’ve tried to get across to people who watch this channel and people, anyone who will listen to me or read what I say or anything.

Uh, is that, um, never look at query costs to figure out if a query is fast or slow, or if one query is better than another. Never look at operator costs to figure out what the slowest part of your plan is. Always get the actual execution plans and validate what you see.

Because remember for all of those costing mechanisms, there’s no actual counterpart. All the costs are pre-execution estimates and none of those, some of those estimates might make no sense. They can be the entire plan cost.

Like for these two query plans, they cost way, way, way, way more than the crappy slow plans that we got that were costed much lower. Right? Granted 2800 query bucks is perhaps not low to most people, but of 30,000 and 11,000, that’s definitely not low to most people. But it doesn’t matter here because those much higher costed plans are much faster.

SQL Server just estimated the work wrong. Right? And this isn’t, this isn’t necessarily an intelligent query processing thing. And beyond that, this isn’t necessarily something that would have been helped with like batch mode or anything.

Because nested loops doesn’t support batch mode. Even in an adaptive join plan, nested loops happens in row mode. Only the hash, only the, only the adaptive hash join could happen in batch mode.

So this is really something that I think Microsoft needs to work on. But until then, until they stop stapling clown nose features like dot feedback and stuff on SQL Server, it’s up to you and me to make sure that we have good indexes, that we try good appropriate hints in our queries when we detect BS from the query optimizer. Remember, query costs are useless.

Query costs are how we got to these plans. Right? Operator costs are how we got to these plans. And us saying, no, those costs are wrong. I just watched what happened.

That, that’s how, that’s how we, that’s how we get good at tuning queries. That’s how, that’s how we make real impacts on our SQL Server workloads. So anyway, hoo-wee.

Sometimes you just get, get talking and you can’t stop. Thank you for watching. I hope you enjoyed yourselves. I hope you learned something. And I will see you in the next video. I haven’t decided, I think I might get these, might get some of this function stuff out of the way first.

I don’t know yet. We’ll see what happens. It’s going to be, going to be fun. I think I’m going to save the sort spills one for last because I need SQL query stress for that. And I’m a little lazy right now.

So I’m going to, I’m going to, I’m going to do these other ones and then we’ll get there. But anyway, thank you for watching.

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.

Performance Tuning Semi and Anti-Semi Joins In SQL Server

Performance Tuning Semi and Anti-Semi Joins In SQL Server



Thanks for watching!

Video Summary

In this video, I delve into tuning semi-joins in SQL Server, specifically focusing on `EXISTS` and `NOT EXISTS` queries. Semi-joins are powerful tools that can simplify complex queries but often introduce performance challenges due to row goals and unexpected query plans. I discuss the importance of proper indexing and demonstrate how adding a hash join hint or creating targeted indexes can significantly improve query performance, sometimes reducing execution time from over a minute down to just eight seconds. The video also touches on the nuances of sorting and the use of batch mode for large datasets, providing practical solutions for optimizing these types of queries in your own work.

Full Transcript

Erik Darling here with Darling Data. Getting a little bit more night recording in. It’s making my green screen lighting a little bit weird, but we’re not going to let that stop us. Apparently the daylight that normally comes in through my office window over there that you can’t see makes a pretty big difference in not casting strange shadows behind me. Who would have thunk it? More light is better when it comes to green screens. But anyway, in this video, this is a video, right? Camera’s on, microphone’s on. Remember to hit record this time. We’re good. We’re going to talk about tuning semi-joins. Semi-joins, of course, generally crop up when you use exists or not exists. And you can have either a semi-join or an anti-semi-join. A semi-join is, of course, for exist queries, and an anti-semi-join is for not exists queries. And both of them are wonderful, spectacular things when used correctly and appropriately. And I’m a big proponent of using exists and not exists because they are wonderful natural extensions of the SQL language that you should be taking advantage of. All the things that you can do to attempt to replace them.

You know, like writing a join with a distinct, a derived join with a distinct in it, or writing a left join where, you know, correlated and then where, you know, the left join to table filtered with like some primary key or not nullable column is null to find rows that don’t exist. Ah, boy. It’s just a lot more work and effort than it’s usually worth. So before we get into that, we of course need to talk about you and me, our future together. If you would like to say thank you for all of the video content and get in on the ground floor of many great and wonderful Darling Day to things that are set to come up in the new year, you can sign up for a membership to this channel for the low, low cost of $4 a month. Not bad.

Not bad. Even with inflation. If you are unable to scrounge $4 out of the couch cushions this month or any month, you can of course interact with my channel in many meaningful, ingratiating ways. You can throw me some likes on the videos. You can throw me some comments on the videos. I do love interacting with each and every one of you almost on a daily basis. And of course, if you subscribe to the channel, you will get these wonderful, wonderful, near instantaneous notifications every time I publish a video. If you find yourself needing help with SQL Server and you need any of this type of stuff done, I am very good at these things. I’m quite good at these things at this point in my career.

So if you are looking for SQL Server help for any of these things, my rates are reasonable. If you need help with something else dealing with SQL Server, I can guarantee you my rates will remain reasonable. If you would like to get some high quality, low cost training, you can do that. You can get mine for that. You can’t get anyone else’s for $150 for life for 24 hours of performance tuning content. Good luck with that.

I don’t even think Pluralsight can beat those numbers, but I don’t think anyone cares about them anymore. Right? A little dicey. But if you use the links in the show description, either to sign up for a membership or to buy my training, those are other good things you can do both for me and you. I will be speaking live with a whole lot of gumption in Seattle, Washington, November 4th and 5th at Past Data Summit, co-hosting two fantastic, glorious days of performance tuning pre-cons with Kendra Little.

And of course, if there is an event nearby you where you would like to single white female me, you can do that by telling me which one might need a pre-con speaker. Because doing the pre-cons, doing the payday and covering some of the travel stuff. I do not make an extraordinary amount of money from pre-cons.

Just covering the travel stuff is a good way to get me to show up somewhere and, you know, I don’t know, whatever you want to do. Smell my hair. Follow me around.

Steal my silverware after I use it. Whatever it is, I don’t know. I don’t know what you’re up to. I don’t know what goes on in that deviant brain of yours. You can do that.

But until then, let’s talk about these here semi-joins. Now, one very, very interesting thing about semi-joins is that they often introduce a row goal into your query. So you might notice if you look at the query that I’ve written here, we have an outer top one for the comments table.

But when we look at the query plan, we’re going to see a rather mysterious top operator in our query plan. Perhaps one that we did not plan on seeing in our plan. An unplanned plan.

So if we look in here, we’re going to see we have our outer top out here, right? That’s our presentation top. We have an order by to make that correct. We have an order by on creation date descending, which is a non-unique column.

And then we continue the order by, extend the order by with the ID column from the comments table, which is a unique column. So you have that tiebreaker in there to make sure that we get consistent results back. If we did not have that in there, especially with parallel execution plans, we could see all sorts of strange things happen.

Now, something that we’re going to wrestle with in all of these plans is sorts. But before we wrestle with sorts, we’re going to wrestle with one of my least favorite plan shapes of all time. And that is when you have a top above a scan.

Right there. We spend 55 seconds in the top above the scan. Now, remember when I said that exists and not exists introduce row goals.

That row goal is there because with exists and not exists, we care not about duplicates. We either find something or we don’t find something. And quite often, SQL Server will use sort of an injected top into the query plan to do that.

We just keep finding the top one over and over and over again. The problem is that every time we run this top one for a row that comes out of here, we have to scan and scan and scan in here. So we end up doing so even though, let’s get this right.

Even though there are only this many rows in the votes table, this is how many rows we end up reading over all of those top scans. Right. That is a fairly brutally large number.

Right. And that is absolutely no fun whatsoever. If we go and we look at the properties of this and we look at this, we will see some really big numbers on all these threads adding up to this really big number on this thread. So SQL Server did not have a good time in here.

Now, part of the reason for this is that we don’t have a good index to support any part of this. We’re going to get to that in a second. But before we do, what you should know is that oftentimes when you end up with a plan of that wretched nature, you can fix it pretty quickly and easily. Without doing any further optimizations, you can fix it pretty quickly and easily.

Now, let’s remember, this thing ran for about a minute without any intervention. If we run this with just a hash join hint, SQL Server will no longer have that nested loops with the top above the scan. We’ll just do a big scan of comments and a big scan of votes, and we will end up in much better shape here.

So big scan over here, big scan over here, hash join in here. The whole thing takes just about eight seconds. So going from one minute to eight seconds is a pretty good improvement right off the bat.

Let’s experiment with indexes a little bit, though. So the first thing that I want to index, because it’s the simplest index to create, it’s the only column that we care about on the vote side of things, is the post ID column.

It’s not a unique column. Obviously, because people can, you know, cast multiple votes on a post. So we can’t make a unique index here, but we can at least create an index to make this part of the query, you know, give that inner side of the query a little something to work with.

The trouble that you run into a lot, though, is once you add a good index, SQL Server starts thinking a little too much about things, and they aren’t good thoughts. So let’s run, well, actually, let’s run this query.

I don’t know why I got the estimated plan there. I was thinking about something else for a minute. So let’s run, get this going. And this runs for about five seconds. We shaved another, like, three seconds off the initial query, but it’s kind of stupid.

The reason it’s kind of stupid is because we fully scanned that nonclustered index over here, and then we fully scanned this clustered index over here. And even though it’s a little bit more efficient, you have to kind of wonder why SQL Server wouldn’t seek into the index that we just created, because that, right, if we create an index, we now have a seekable thing for that exist clause.

So let’s see what that query plan might look like. Let’s get the estimated plan here. All right, so now we have this one here with a top above a seek.

Let’s see how this goes. All right. I am mostly happy with this. We put this down to 3.5 seconds, right?

But now we have this sort of interesting thing over here. Most of our problem in this query is SQL Server needing to order by creation date descending and comment the ID column in the comments table ascending.

Okay. Well, well, you know, it’s kind of weird, right? Isn’t it? Put this stuff in order from the comments table.

Okay. Well, I mean, I guess we can do that over here. We could do that over here after we, like, found stuff. But, you know, SQL Server doing this over here. Sometimes that’s a good plan. All right. So let’s create an index on the comments table.

And what this index is on is it’s on post ID, right? Because post ID in the comments table is what we’re correlating on to the post ID in the votes table. And then we’re going to put creation date second.

And the hope here is that, you know, if you’ve watched any of my other videos about how indexes hold data, that for every post ID we find, which is within a quality predicate, the creation date column will be in order. And since this table has a clustered primary key on the ID column, that ID column, and we have a, this nonclustered index is not unique.

The ID column from the comments table will be a hidden third key column here. So we would technically have post ID and creation date and ID in this index in the order we want. All right.

So let’s, let’s create that. Let’s see what happens here. Let’s go with this. Let’s roll with all this fun stuff. I’m going to create this index and see what happens.

It might be a, might be a real fun time, right? Okay. Here we go. You got it.

We got our new index in. All right. And let’s run this. Let’s see here. This appears to be, this appears to have gotten worse with an index, doesn’t it?

Sure did. 14 seconds. What happened? Well, you’re going to see what happens.

It’s SQL Server chose a serial merge join plan. Look at this garbage monster monstrosity that SQL Server has chosen here. Because we now have both of our join keys in order, SQL Server was like, oh, I don’t need to sort anything.

I’ll just, just do a merge join. But even SQL Server sometimes knows that a parallel merge join is the worst thing in the world. And so it gives us this, this serial plan.

Even worse, what do we get over here? We still have a, the votes table is still on the outer side of the query with an, with an index scan on it. And the comments table is in here with an index scan on it.

We didn’t even seek to any of the stuff that we cared about. We just use the ordered nature of things to, to, to, to, to, to give us that serial merge join plan. Now, this is where things get kind of annoying is that if we, if we just, if we had a force seek hint to the comments table and we just try to get an estimated plan, SQL Server says, no bueno.

We, we are out of buenos. You can have no buenos. You, you, you, no buenos for you.

The query processor could not deal with this, which, you know, it’s kind of weird because without the force seek hint, you know, we had the comments table on the inner side of the join. And you would think that SQL Server could just take, you know, stuff from here and seek into it with the, the post column over here. But apparently not.

Now, if we add a force seek hint on the inner side and we run this, this is what our plan turns into. But we still have this sort here on the comments table. All right.

So this is okay because we’re down to about three seconds now. We’re still, we have about 1.5 seconds reading from the comments table and now about 1.5 seconds on the sort. And the past, the sort had spilled a bit and things were a little bit weird.

This is about as good as we’re going to get. All right. Honestly, as far as query tuning, this thing goes. And this, this took kind of considerable work to get from, you know, a minute to eight seconds, which is so far the biggest jump. But to get from eight seconds to like three seconds, you know, we had to add two indexes and now we need a force seek hint.

And things are just, things are just a little rocky. If I’m going to be honest with you, if I’m really tuning a big query that does all this stuff, what I want is batch mode. All right.

But just use, like get batch mode involved somehow. That’s really what I want to go after. But in this one, we’re going to focus just a little bit on some of the indexing pitfalls that can happen in here. So because we need to sort data a little bit differently, and I have, I accidentally deleted that from my index definition.

Sorry about that. All right. We’re going to change our index a little bit to be on creation ID descending, ID ascending, and then post ID as the last column in the index.

All right. And we’re going to drop our existing index with the wonderful underused drop existing equals on index option. And now we’re going to see how things go with this plan.

Let’s get the estimated plan here and see what happens. Now we have a serial nested loops join plan with no sorting. And if we run that, we finish things up just about instantly.

So if you’re tuning queries that use exists and not exist, right, they’re going to have a semi join of some type. Exist will be a plain semi join. Not exist will be an anti semi join because you are finding stuff that isn’t there.

They sometimes take, they sometimes need some extra help to get them to be reliably nice and fast. Sometimes that extra help is just throwing a hash join hint on there. Sometimes that extra help is just getting batch mode involved.

If you have two very, very large tables, batch mode is going to do you a hell of a lot more good than all of the indexing in the world. These tables aren’t quite big enough to qualify for that. Batch mode does do really well on them.

But, you know, I’d rather show you a little bit of the indexing stuff. The other thing that you can do with the indexing is pay really attention to really close attention to the query plans. If you end up with these types of plans and you have all sorts of sorting going on in them, you might need to adjust your indexes in a way that is a little bit counterintuitive.

And what I mean by that is you might need to sort of index towards the sort with these columns rather than index towards the join with this column. Remember that when we added this column in, things got a bit better.

Not like spectacularly faster. We saved like two and a half or so seconds on things. But it wasn’t really like spectacular. And then when we added a force seek hint in, we ended up with that awful serial merge join plan.

So sometimes depending on what the problem, like really, if this video were to have like a great title, it would be how to index for what the problem and the plan is. The issue is that this video would take me, that video would be months long.

So this is just one example. So this is just kind of indexing to help you tune exists and not exist queries. And some of the things that you might see in query plans along the way.

In my case, for the query that I’m running, my best set of indexes was, of course, one on the votes table on the post ID column, right? Because that gives us a good, clean, ordered path into the data that we’re trying to figure out if it exists or not.

And then it was the final index that we created down here where we geared our index towards sorting the data the way that we needed it and then having the column that we care about for correlation in here. Because remember, when we tried to put a for seek hint on the comments table, we got that optimizer error anyway.

So there was no way SQL Server was seeking into the comments table and doing anything helpful with an index that led on post ID. But there was just no use in that. So with the index that we have down here, once again, we get a fairly simple, easy query plan, just a plain serial nested loops plan.

And we end up finishing this very quickly because now we don’t need to sort data. And now we have a really efficient way to locate the data that we care about on the inner side here. So no sort, top above a seek, simple top for the presentation, and boom, we have a fully tuned not exist query.

So with that out of the way, once again, from me and bats, thank you for watching. I hope you enjoyed yourselves. I hope you learned something.

I hope that if we ever meet, you bring me Pez because my Pez dispenser is empty. I’m just kidding. I don’t actually eat candy. I have no sweet tooth whatsoever. If you brought me a salt lick, I would be far more appreciative.

That’s my problem in life. The salt. Savory and the salty.

Anyway, I’m going to get going because I obviously have some other tabs to close out. So we’re going to get those recorded, and I will see you over in the next video. Thank you for watching.

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.

Join Me And @Kendra_Little At @PASSDataSummit For 2 Days Of SQL Server Performance Tuning Precons!

Last Year


Kendra and I both taught solo precons, and got to talking about how much easier it is to manage large crowds when you have a little helper with you, and decided to submit two precons this year that we’d co-present.

Amazingly, they both got accepted. Cheers and applause. So this year, we’ll be double-teaming Monday and Tuesday with a couple pretty cool precons.

You can register for PASS Summit here, taking place live and in-person November 4-8 in Seattle.

Here are the details!

Day One: A Practical Guide to Performance Tuning Internals


Whether you’re aiming to be the next great query tuning wizard or you simply need to tackle tough business problems at work, you need to understand what makes a workload run fast– and especially what makes it run slowly.

Erik Darling and Kendra Little will show you the practical way forward, and will introduce you to the internal subsystems of SQL Server with a practical guide to their capabilities, weaknesses, and most importantly what you need to know to troubleshoot them as a developer or DBA.

They’ll teach you how to use your understanding of the database engine, the storage engine, and the query optimizer to analyze problems and identify what is a nothingburger best practice and what changes will pay off with measurable improvements.

With a blend of bad jokes, expertise, and proven strategies, Erik and Kendra will set you up with practical skills and a clear understanding of how to apply these lessons to see immediate improvements in your own environments.

Day Two: Query Quest: Conquer SQL Server Performance Monsters


Picture this: a day crammed with fun, fascinating demonstrations for SQL Server and Azure SQL.

This isn’t your typical training day; this session follows the mantra of “learning by doing,” with a good dose of the unexpected. Think of this as a SQL Server video game, where Erik Darling and Kendra Little guide you through levels of weird query monsters and performance tuning obstacles.

By the time we reach the final boss, you’ll have developed an appetite for exploring the unknown and leveled up your confidence to tackle even the most daunting of database dilemmas.

It’s SQL Server, but not as you know it—more fun, more fascinating, and more scalable than you thought possible.

Going Further


We’re both really excited to deliver these, and have BIG PLANS to have these sessions build on each other so folks who attend both days have a real sense of continuity.

Of course, you’re welcome to pick and choose, but who’d wanna miss out on either of these with accolades like this?

twitter
pretty, pretty, pretty, pretty good

You can register for PASS Summit here, taking place live and in-person November 4-8 in Seattle.

See you there!

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. I’m also available for consulting if you just don’t have time for that, and need to solve database performance problems quickly. Want a quick sanity check before committing to a full engagement? Schedule a call — no commitment required.

SQL Server Performance Problems When Joins Have No Equality Predicate

SQL Server Performance Problems When Joins Have No Equality Predicate



Thanks for watching!

Video Summary

In this video, I delve into the world of non-equality predicate joins in SQL Server, specifically focusing on nested loops joins without equality predicates. You’ll see how these can lead to significant performance issues, even when indexes are present. I explore why parallel nested loops queries with guaranteed one-row outer parts perform much better and discuss the downsides when this guarantee is broken. Additionally, I share practical solutions, such as introducing computed columns or using hash-based joins, to mitigate these problems and achieve more efficient query plans. If you’re dealing with similar performance challenges in your own SQL Server environment, be sure to check out my training resources for expert guidance on tuning and optimizing queries.

Full Transcript

Your friend, Erik Darling here with Darling Data. And today, actually I should say tonight’s video. We’re doing some night recording for reasons that I don’t have to explain to you. We’re going to be talking about da-da-da-da joins with no equality predicates because, gosh, have I seen these just cause a disgusting amount of performance problems in my time. Part of the problem with joins with no equality predicates is that it’s a disgusting amount of performance problems in my time. So, the problem with no equality predicates is that it’s a disgusting amount of performance that I’m using in my time, but also I want to be talking about. And what we’ll find is that even with great indexes in place, there is just absolutely no helping some queries. The other thing we’re going to find is that sometimes we need to introduce some sort of hacky, hashy, equality-predicate type thing, so that we don’t have to be able to do that.

we can get reasonable performance out of our queries. But before we dig into all that, you know what time it is. It’s time for me to tell you how to give me money.

You can sign up for very low-cost memberships to say thank you for all the videos, all the time and stuff that goes into these miraculous things that has bit my tongue. If you can’t, they’re like $4 a month, and there’s a link in the description that you can click on to join the channel.

If you’re all out of $4, you can always like these videos. You can engage and interact with me in other ways that I find appealing. You can comment on the videos.

The comment sections have been great lately, so thank you to everyone who continues to do that. And you can also subscribe to the channel for the absolutely low, low price of $0. and you can get notified also for the low price of $0 every time one of these things goes live.

If you are the type of person who says, gosh, we could use Erik Darling’s help with our SQL servers, these are the types of things that I excel at.

I’ve done over 700 of them now just with Darling Data as an independent consultant. Well, I mean combined, not like each. That would be nuts.

And that’s not even my entire consulting career. Imagine that. So you can hire me for all those things. If you would like some low-cost, incredibly high-quality SQL Server training, all the performance tuning in the world, really, I have all of it.

I mean, what else is there other than beginner, intermediate, and expert? I can’t think of anything. And you can get it all for 75% off, which means the cost is about $150 USD.

And what do you call it there? That’s for life. Yeah, you don’t have to resubscribe to any of the stuff you get in this bundle. It is everything for all eternity.

So that’s nice for you. As far as upcoming events go, where I will be live and in person, shaking you upside down by your ankles, emptying out your pockets like a schoolyard bully, I will be at Pass Data Summit, November 4th and 5th, co-hosting two days of performance tuning grandiosity with Kendra Little.

If there’s an event near you that could use a pre-con speaker, let me know because I got pre-cons and I will travel. So with that out of the way, let us engage in festivities.

Let us have fun with our SQL Server selves. For some reason, the click wouldn’t work, so that all went black. That was fun.

So let’s make sure we have no indexes. And the first thing I want to show you is that, and this is actually kind of like probably a little bit more than you may have bargained for when you started watching this.

But there’s something really interesting that can happen to parallel nested loops queries or parallel nested loop query plans when you are guaranteed to have one single row on the outer part of the nested loops join.

And this is not something that happens when you don’t. If you have more than one row, I mean, if you have zero rows, I guess you don’t have to worry too much about it.

But if you have more than one row, things can get a little weird. But first, let’s take a look at this query plan. So this finishes relatively quickly. And the reason why this finishes relatively quickly is because there’s exactly one row in the user’s table with an ID of 22656.

Now, granted, user ID 22656 is Mr. John Skeet. You have a million plus reputation points on Stack Overflow. Even in the 2013 copy, he has that many.

So I think, anyway, it’s a big number. But if we look at the query plan, something really cool happens. At least I think it’s cool.

You might not think it’s cool. But if we look through this, we have sort of a typical guaranteed one row nested loops, parallel nested loops plan, where we get our one row from the user’s table.

And then we distribute that row out to our streams. And this is, of course, broadcast partitioning, which means that the one value from that row ends up on multiple threads, well, I mean, on dot threads, really.

And what happens on the inner side of the parallel nested loops join, and the reason why this is unique is because our row is unique. And when we look at the number of, the way that the rows get distributed on parallel threads, they do not, normally you would see 17142169 on every thread, because that’s what the inner side of a parallel nested loops join does.

It runs dot copies of whatever you do on the inner side of the nested loops join, which can be awful sometimes. Other times, when you’re guaranteed one row from the outer portion, you can end up with a much more, usually a much more efficient query plan, where those rows do get spread out.

So if you added up all those numbers that I just stuck my hand into, like a weirdo, you would get 17124169. So that’s cool.

What happens when you don’t? When you don’t guarantee one row? So the first thing I want to show you is that if we look at all the users in the users table, whose IDs are between 22656 and 22666, there’s only one other of them, right?

There’s only one other ID in there. But if we look for this, where ID is between 22656 and 22657, SQL Server is still going to expect two rows to come out of there.

Because of that, our query plan is going to change drastically and dramatically. It’s going to be awful.

And I don’t know, something about night recording is making the shadows over here a little weird. I don’t know how to get that to be any less weird right now. I can just close my arms so you can’t really see behind me. And then I can just do weird little robot T-Rex type arm things.

And I don’t know, that won’t look awkward at all, will it? No, not one bit. But if we look at the query plan, it changed quite a bit.

It took quite a bit longer. So this took about 22 and a half seconds. Yeesh. Yeesh. That didn’t do good. And just about all of that time is spent in between these two things right here. Scanning the clustered index to get new rows out and spooling those rows into a lazy table spool.

I’ve got lots of videos about how lazy table spools work. The short of it is that SQL Server takes the values that come out of here and it sends them over here. And then the first time this executes, SQL Server runs the clustered index scan over there.

Let me zoom in so my pointing is a little bit more effective. SQL Server runs the clustered index scan and gets whatever rows it needs for the predicate that gets passed over here. When it’s done with that, it truncates the spool and the next row that comes in, it repopulates the spool by scanning the table or usually scanning.

Generally, if you have a seek over there, you won’t see a table. Sometimes you will, but in general. Hitting this, let’s just say, hitting this object to get the next set of rows out to populate the table spool with is what happens.

This just takes a very long time. And part of why it takes a long time is because we end up with very, very lopsided parallelism. If you look at this, all of the rows end up on a single thread.

This is quite abnormal a lot of the time, but it can be very normal when you have these sorts of plan shapes. The reason why this happens here is because we’re looking for a single thing at a time. And we just get some really unfortunate data distribution stuff happening in the post table.

So we end up just putting all of our work on one thread for the two iterations of the nested loops joint. Because even, well, really the one iteration, because only one row comes out, even though we expect two rows. But SQL Server sort of like getting ready to defend itself against two rows means that we lose that guaranteed one row on the outer side of the nested loops thing.

And that sort of messes us up a bit. You’ll see that the partitioning type in here changes from broadcast to round robin. Meaning that this thing just, you know, puts things on threads as they come out.

And that’s, you know, kind of not good for this situation. Of course, having an index on the post table for last activity date is pretty helpful in both scenarios. One thing that is kind of a downer, though, is that with that good index in place, the original query slows down a bit.

This goes from finishing just about instantly with a parallel plan to taking a bit longer with a serial plan. Now, if you remember, the parallel plan spread out, you know, those dot threads pretty nicely. And we did all this work just about instantly.

Now we lost a little bit of efficiency here, right? This is about 1.7 seconds now, which isn’t great. But I think, you know, generally the efficiency that you gain with this query, where this thing no longer takes like 22 seconds to run, this thing takes another 1.7 seconds to run.

It makes it worthwhile. What you have to watch when you are writing queries like this that do not have a direct equality predicate, right? The only thing that our only predicate in the join clause is where the last activity date on the post table is between the creation date and the last access date on the post table.

You have to be really careful that your join keys are well indexed for this sort of thing. And, of course, the, you know, bigger and, you know, more involved your queries are, the harder that gets to, you know, get the harder that gets to really index for. So let’s get rid of these indexes.

And let’s talk about a slightly different kind of range query. Now, I can’t actually run this one to completion. This thing basically never finishes.

I’ve never, never spent too, too long trying to get this to run. I think the longest I let it run for was about 20 minutes. And it really just, you know, really just made the room hot. The laptop was sizzling.

I could have used it like a griddle. But this will basically never finish. It’s a real unfortunate sort of situation. And it’s made even more unfortunate by the fact that, you know, SQL Server asks for some kind of silly indexes.

So if we ran this multiple times, SQL Server would eventually suggest an index on display name. And I’m going to, the original index it wanted was on post type ID, comma, last enter display name. I’m just going to put this in the where clause here to, you know, shortcut having to do all that stuff.

Because it effectively gives you the same thing. But what’s really rough is that unless we write our query with an equality predicate like this. Right?

Like let’s say that we, let’s actually give you a slightly better example. Let’s do this first. And let’s look at what SQL Server comes up with for a query plan. Seeks into the post table.

And then does all of this sort of weird work. And then nested loops join to hit the users table. This will never finish either.

If we change, if we stick a force order hint on here, and we look at what happens, we’re going to see a query plan that looks a lot like the one that we saw up above before we had an index. The problem is this one basically never finishes either. This one, you know, just does really poorly.

And yeah, it basically just never finishes. A lot of the problem is that you have, you know, two point, like I said early on in the video, when you have a join without an equality predicate, you are, you are basically at the mercy of nested loops. So a parallel nested loops here where you are not guaranteed one row on the outside means that you’re going to spool a whole lot of rows in here.

Right. And that’s, that’s pretty painful. So you have 2.4 million something rows here.

That means 2.4 million rows are going to end up on each thread. And if you look at this number, you can see why this is going to take a very long time. Right.

SQL Server, like the estimate here is actually kind of close to reality because it’s that 2.4 something million number times eight. Right. So this actually is how many rows end up on each thread every time you go into this side of the join. And that table spool just does not buy you anything there.

The only thing that I’ve ever found that helps at all with this is to give SQL Server some kind of a quality predicate. Now, if I were doing this, like with a client to really help them get a query running faster, I would probably make computed columns that do something like this so that I can have a sargable equality predicate for this join.

But in this case, this actually gets us good enough performance. The problem is that if we don’t add that force order hint in, if we just do this, we end up with this awful execution plan again. With the force order hint on there, we end up with a much more favorable execution plan.

Now, SQL Server, because we have that equality predicate, we can at least get a hash. Let’s join here. Now, the logic of this may not be 100% correct because I’m saying where the display name equals a display name and the display names like this.

There are all sorts of ways that you might need to logically look at this to figure out if what we’re doing here is exactly correct. One other thing that you could do is find like the min length of display names in the users table and just match on the minimum length of one being equal to the minimum length of another, which is like, I think it’s like three characters over in the users table.

There are all sorts of ways that you can do this or look at this or approach this. You might even try hashing some of it or you might even try hashing the display names or something like that. But the goal is to just get something that’s an equality predicate into the join clause so that when you run this, SQL Server has other options for join types.

And when you run this, it actually finishes pretty quickly with those indexes in place. Is this result correct? I don’t know.

You know why I don’t know? Because none of the other queries finish. So that’s fun there. But it was my attempt to kind of give you some advice on how to start tuning these queries and how to start getting at least some semblance of a reasonable query plan back. Like I said at the beginning of the video, or I don’t actually forget when I said it, joins without equality predicates have really caused a lot of performance problems that I’ve seen over the years, especially when there are really large tables.

And almost without fail, there’s no good indexes to support what you’re actually doing. And almost without fail, it takes a lot of effort to get these queries to do anything reasonably fast. So I’m going to wrap this one up because I’ve got a few other videos that I need to record this evening.

So I’m going to get this one on the books. Thank you for watching. I hope you enjoyed yourselves. I hope you learned something.

I realize that the outcome of this video is a little disappointing because there really is a lot of thought that has to go into what makes sense for any sort of a quality predicate in here. And that can take a lot of tinkering and tweaking. And that’s just a lot of time and effort that you’re going to have to spend, you know, reasoning with the data that you have in your tables.

For me, this is probably a close enough approximation that actually gets some results back and gets the query to finish. But, you know, you might have a completely different set of requirements that make this absolutely useful. So, from me and Bats Maru, my only friend in the world, thank you for watching and I will see you in the next video.

Have a good night.

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.

A Little About DOP and Bitmaps In SQL Server

A Little About DOP and Bitmaps In SQL Server



Thanks for watching!

Video Summary

In this video, I delve into the effectiveness of bitmap operators in parallel queries and how they can impact query performance. By analyzing a specific example from StackOverflow’s database, I explore the relationship between the degree of parallelism (DOP) and the efficiency of these operators. Through various DOP tests, I demonstrate that while higher DOPs may not always double execution speed, they can significantly reduce processing time, especially in larger datasets where row counts are more substantial. I also highlight how adjusting DOP to optimize bitmap performance can lead to better overall query efficiency by reducing the amount of data processed and joined, making SQL Server’s workload management more effective.

Full Transcript

Erik Darling here with Darling Data, the one and only, as far as I know. I did recently get an email that someone was trying to file a patent on Darling Data, and that if I paid this random lawyer a bunch of money, he would prevent it from happening. The crap you get from LinkedIn, you sign up for LinkedIn, and you put anything other than, like, I’m just an employee, man, leave me alone, in your title or experience, or like, you put a business on there. This is the kind of garbage that you have to look forward to. I also get lots of spam emails about VOIP phone lines from my office. I’m like, you’re gonna be real disappointed. I don’t know, some guy keeps trying to sell me wires, but mostly it’s just a bunch of emails like, hey Erik, call tomorrow. And I’m like, no, call never at any time. So, yeah, life on the internet sucks. We all knew that. Cool. In today’s video, we’re gonna talk about DOP, degree of parallelism, in bitmaps. I don’t think bitmaps is an acronym for anything. If it is, I’m way out of line. So, oops, I hit the wrong button. That was escape button. Hey, there we go. Pretend that didn’t happen.

But before we talk about the exciting world of DOP and bitmaps, the rich tapestry of DOP and bitmaps, we’re gonna talk about how you can keep me alive. You can sign up for a low-cost membership for the channel. It’s like four bucks a month. It’s a nice way to say thank you for getting, like, five free videos a week. I mean, I realize that at four dollars a month, they are no longer free, but the average cost per video is still really low. If you do that math. If you are unable to fork over four dollars a month and you would like to say thank you or show some appreciation in a slightly different way, you may subscribe to the channel exactly once, unless you have a, like, a bot farm or something, but, which would be cool, but wouldn’t really help me. Wouldn’t really help me with this part. You can also like the videos and comment on the videos in case you weren’t aware that on the internet there’s thumbs up buttons and ways to type into things and say words and have them there permanently.

If you need help with SQL Server, these things, they’re fun for me. Doing health checks, performance analysis, hands-on tuning. I mean, the emergencies are less fun. They’re more stressful, but mostly for you, because, I mean, I enjoy myself, even during an emergency. It puts me in a calm and soothed state. Or if you want developer training so that you have fewer emergencies and you freak out less, that would probably be great for you. I can do all of those things at a reasonable rate. If you would like some low cost, high quality, the highest quality, I mean, golly, golly, I can’t, I can’t even begin to stress how high quality this training is. You can get 24 hours of it for about $150 for life with that, that coupon code. There is also a link for that in the, in the show description.

If you, there’s a link for the membership, in case I forgot to say it, and a link for the training in there. So there’s just links galore in those descriptions, in case you’re unaware that on the internet you can link web pages from another place. So there’s that. If you want to see me live and in person, you have two options right now. You can come to Seattle November 4th and 5th and see me and Kendra do two days of performance wondering on, not wondering, wondrous performance tuning on SQL Server. You can, you can do that. Or you can tell me about an event near you that you might actually go to. And I can say, hey, event, do you need a pre-con speaker? And would you like it to be me? And they might say yes.

And then I might show up and do it. So that’s what you got right now. With that out of the way, let’s go do some SQL. Let’s do a query, my friends. You know, query real hard here. So the purpose of today’s video is to tell you, tell you a little bit about, uh, DOP and bitmaps. So the first thing you should know, uh, and this is, this is outlined, um, in a, in a fairly old Paul White article, uh, where even in a serial plan, uh, that gets a hash join, you, there’s like some invisible bitmap. Uh, I can’t imagine that it does much of anything because, um, well, you’re, you’re going to see why I imagined that here, but, uh, he’s way better at explaining it than I am.

Uh, what I’m going to try to explain to you is how the degree of parallelism of your query can make bitmaps more or less effective. And basically the, the tall and short of it is that when you have a tall DOP like eight or 16, bitmaps are more effective than if you have a short DOP like two or four. Uh, I skipped six on this one because six didn’t really change much of anything.

So I’ve got the same query four times. I got max DOP two. Count them off. Do pushups. I want you to do DOP pushups right there. All right. So you owe me two pushups and here we have max DOP four. And this is where you owe me four sit-ups. All right. And here’s DOP eight. And this is where you owe me eight jumping jacks.

All right. And then we have DOP 16 and this is where you owe me $16. Remember those numbers because we’re going to look at them again when we look at the query plans too. All right. So way up at the top here, we have a parallel execution plan.

And, uh, there is a bitmap in this plan. And remember, if you’ve watched other videos of mine about bitmaps, you know that the bitmap, well, it gets created here. Where it gets used is typically down somewhere in here. Some bitmaps can get stuck at the repartition streams. Some, some bitmaps get pushed right down to the, uh, the table that we’re hitting.

Which is the case for this one. We have this predicate. We have an in-row bitmap, which means that SQL Server can start filtering out rows way down when it starts reading pages that don’t, that, like, obviously don’t match the bitmap. Even with, you know, bitmap in place under certain conditions, you still have to have a residual thing here. We don’t though.

Because, uh, the ID column in users, uh, is a, uh, not nullable integer. And the, uh, user ID column in the badges table is also a not nullable integer. So the not null integerness of those two columns, it would also work for not nullable big ints, uh, and some few other data types.

It’s not strings, uh, but numbers generally. Yeah. Uh, you could get similar behavior where you don’t need the residual predicate there because SQL Server’s like, I got it. So, with that out of the way, uh, let’s look at how effective that bitmap was.

And the answer is, not very. Uh, that bitmap was not able to filter out most of these rows. We go from 2465710 to 2465701.

So that bitmap at DOP2, where we, where we built exactly, like, two hash buckets, uh, well, uh, that got rid of nine rows. So, not very good there. Um, I don’t know, maybe SQL Server had, like, more, maybe SQL Server thought that would do better.

I don’t know. But that, that clearly stinks, right? Not, not a good time there. If we go look at the DOP4 query, well, we got a few more out.

At least we got, I don’t know, about four, 39 rows that time, right? 2, 4, 6, 7, 5, 1, 0 to 2, 4, 6, 2, 4, 6, 5, 6, 7, 1. So, we, we got down by about 39 rows there, I think.

If I’m, if I got, if I got my finger maths right, um, that, that sounds good. 39. Maybe I’m off by 10, one way or the other. And the high, high school dropout thing makes on-the-fly math a little tough.

So, DOP2, right here, this one, right? Remember, you owe me two push-ups once again. Uh, DOP4, again, not terribly effective.

You owe me four sit-ups here. And then if we scroll down a little bit, we have this query at DOP8, where you owe me eight jumping jacks. This one does a little bit better.

Not a lot better. Slightly better. Uh, this one at least changes the second number, right? We went from 2, 4 to 2, 3. So, that, that one actually got us down a little bit.

So, the DOP8 bitmap so far has done the most work. Good job, DOP8. Now, in real life, most queries, uh, I would be happy to stop at DOP8 and be like, well, you know what? Uh, you know, there’s usually kind of diminishing returns after DOP8.

Sometimes it’s just, it’s just not as good, right? Like, you just, like, you’re not, you don’t keep scaling linearly as you add threads after DOP8. The stuff like that totally happens all the time.

I know, because I, I, I test stuff like this all the time. Uh, it’s part of my job, figuring out what the best set of query stuff to do is so that they go as fast as they can. Sometimes part of that is testing higher DOPs to see if anything remarkable in the plan changes.

Now, for this plan, and I, look, for this plan, right? And I, I’m, I’m totally with you on this. Uh, it, it, it finishes and, well, I mean, so let’s start back up at the top a little bit.

But this one at DOP2, right, because it only uses two threads, this takes about three seconds. So DOP2, obviously not a very effective use of parallelism or bitmap. This one down here at DOP4, that, that did get quite a bit better than the one at DOP2.

Just still not a very effective bitmap. This one’s 1.18 seconds, 1.181 seconds. At DOP8, we do still just about twice as good.

We go from 1.1 to 673. That’s close enough to twice as fast for me. But that’s still without even getting rid of twice as many rows with the bitmap, right? So usually when I’m dealing with queries that do this sort of thing, the differences are far more profound.

There’s a lot more rows flowing around. This is just a StackOver 2013 database with a little bit of stuff in it. The data that I deal with is typically much, much larger, which makes things like testing higher DOPs, like, a lot more attractive in a lot of scenarios.

Because I want more, I want to take those, like, you know, it’s like from, you know, if you’re at DOP8 and you have an 800 million row table, you’re still looking at, like, 100 million rows per thread. At DOP16, you’re at 50 million rows per thread. And that you can do a lot more work across that, you know, it’s a lot more effective spreading those rows out further.

So, like, that was, again, that was high school math, right? That was straight division, baby. Mmm.

Felt good. Felt real good. This one down here, it gets faster, but not twice as fast. We go from about 700 to about 4.5, which, you know, give or take 100 milliseconds, that’s still a pretty good reduction. And, you know, if in a much, much bigger query, you would find, like, that that difference might be more profound.

It might be, like, you know, 4.4 and a half minutes versus almost 7 minutes, right? So, like, that would be noticeable if you were dealing with, like, a big process that did a lot of ETL and moved a lot of stuff around. You’d be, like, you know, even bigger time span.

Let’s say that was almost 7 hours versus 4 and a half hours, right? There’s, like, a lot of timescales where it would make a difference that just doesn’t make a difference in milliseconds. But look at this one, right?

I don’t know how much the bitmap made a difference here. It’s hard to tell. But if you look at this part, oh, gosh darn it, tooltip, why do you do that to me? Why do you have to bury it to the hilt?

Ugh. Look at this. Look how much more effective that bitmap got. That’s a little over 700,000 rows, I think.

Right? A little over 700,000 rows. So, going, like, from here where we got, like, I don’t know, maybe, oh, that’s the wrong button. There we go.

Control key. Now we got it. From here where we go from, like, 2.4 million to 2.3 million, it’s not that great. But this one down here where we go from 2.4 million to 1.75 million, or 2.4 to 1.7, that’s probably a much more fair comparison. Because that is 2.46 and that’s 1.75.

So, you know, decent baseball player money, I guess. If you look at that, then, like, that higher dot contributed to a much more effective bitmap operator. So, what is the takeaway here?

If you have parallel queries like this, and you’re able to, you know, sort of fit this basic plan shape where, you know, you probably scan and index because you’re not going to do a lot of seeking with hash joins usually. You can if there’s other indexing stuff involved and there’s other where clause stuff involved. But just, you know, let’s just say you have a big parallel seeker scan over here, and you have a big parallel seeker scan down here, and somewhere in between them, you have a bitmap operator, which is going to look a little bit different if your queries are running in batch mode.

If you’re running in batch mode, you’re not going to see a bitmap operator in the query plan. You’re going to have to, like, right-click on the hash join operator and go to the properties, and it’ll say, like, is bitmap creator equals true if it did create a bitmap. So, this is like a row mode plan.

If you’re running stuff in batch mode, it’s going to be a little bit different. But let’s say that you have a seeker scan and then a bitmap and then another seeker scan, and there’s a hash join involved. It’s something to look at how effective that bitmap is.

Usually, the lower the number of rows that come out compared to – so, like, bitmaps don’t always do cardinality estimation. Or, rather, bitmaps can make cardinality estimation look like it’s just really wrong here. But really, it’s the bitmap being really effective that makes it look really wrong.

It’s just, like, the better a bitmap gets, the worse this estimate looks, right? Because this thing is basically just, like, table cardinality, right? Because there’s, like – we’re not, like, a seeker scan or a predicate down here.

We’re not – there’s no, like, where clause that’s saying, like, you know, where users’ reputation is greater than 100,000. There’s nothing to, like, you know, say it might be less than table cardinality, but we’re reading out of there. The only thing that you get is the bitmap, and Seawil server may not know how effective that bitmap’s going to be, obviously, because it uses it at DOP2 where it sucks.

And down here – so, like, the worse this estimate looks, the better the bitmap was usually, right? As long as there’s no other predicates involved that might have been right or wrong or somewhere in between. So, when you’re messing with queries that have this particular pattern in them, sometimes it is worth trying to adjust the DOP up to a higher number to see if, A, the query gets meaningfully faster, right?

Because adding more CPU threads in, spreading the rows out on those CPU threads generally can get more efficient up to a certain point. The other thing is that sometimes it can make that bitmap way more effective, which means you’re reading far less out of this table, and you’re putting far less into the join up there. So, you know, there’s a lot you can do that, you know, makes SQL Server’s job a lot easier as bitmaps become more effective.

And again, going back to the example of, like, you know, bigger ETL processes, larger overnight batch stuff, even, like, big reporting queries that may run for a long time, being able to get this sort of impact on both the overall time and the effectiveness of the bitmap, which probably does contribute to it a bit, at least a little bit, can really help with performance.

So, keep all that stuff in mind as your tuning queries, because, gosh, if I have to do it, you have to do it, too. It’s not fair if only I have to do all this stuff, remember all these things. I have to write stuff down and record videos, and I have to, God, I have to read my own writing sometimes, and I have to watch my own videos sometimes, and the one thing I hate doing is hearing my own voice.

Not fun. So, anyway, that’s about enough for this one. Thank you for watching.

I do so very much hope you enjoyed yourselves, and I pray to whatever might be out there that you learned something. If you didn’t, I don’t know what you did for the last 16 minutes. You don’t have to tell me.

Personal choice. Just sit here and watch this if you’re not learning anything. And so, enjoy yourselves. Learn something. Hire me to do stuff. My rates are reasonable.

I am going to trademark that phrase, because I feel like it is now synonymous with the Darling Data, Erik Darling brand. And I will see you soon in another video where I will talk about something with equal effervescence. So, there we go.

Thank you for watching.

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.

Inconsistent Error Handling By SQL Server

Inconsistent Error Handling By SQL Server



Thanks for watching!

Video Summary

In this video, I delve into the fascinating and often perplexing world of inconsistent error handling in SQL Server. Specifically, I explore how certain errors can cause entire batches to roll back while others only affect individual statements within a transaction—demonstrating this through a series of examples with a table called `transaction_test`. By walking you through these scenarios, I aim to highlight the importance of setting `XACT_ABORT` on for stored procedures and scripts that handle multiple modification queries. This practice ensures more predictable behavior and helps avoid unexpected rollbacks due to conversion errors or other inconsistencies.

Full Transcript

Erik Darling here with Darling Data. And, boy, we’re having a great day here at Darling Data Studios, getting all sorts of important work done. And I’m going to showcase some of that very important work to you today in this video about inconsistent error handling in SQL Server. I don’t mean your inconsistent error handling. I know you. I know you handle errors inconsistently anyway. We’re not talking about that. We’re talking about how SQL Server handles certain errors inconsistently and how that can affect what rolls back in a batch of queries. It’s going to be fun, I promise. You’re not going to lose your mind anyway. It’s going to be a great time. So, before we get into all that, let’s do the normal spiel and routine here because this is, gosh, this is just my favorite part of every video. Getting to sell myself a little bit, one piece at a time.

You can subscribe to a very low-cost membership to support my continuing to record these videos. I’ve got to pay for the electricity somehow, I guess, right? There’s a link to sign up for the memberships down in the video description. You can get one for like $4 a month. In the future, there will be more stuff for subscribers. But right now, I’m dealing with a lot of pre-con material and writing and all this other stuff and I just haven’t had time to build that up yet.

But, if you get in early, you’ll get more stuff because that’s how much I care about you. All of this content is, of course, free. So, if you don’t have the $4 a month or maybe, I don’t know, you just hate me that much and you won’t like spite watch these videos and you just want to like, I don’t know, keep making me waste electricity, You can do other things to show me how much you hate me, like subscribe to the channel so that you can get spite notifications every time I publish a video.

You can give me a spite thumbs down if you want. I mean, go ahead. You’ll be like, you know, the only one. And you can leave me spiteful comments if you also are feeling particularly outrageous on that day. If you need help with SQL Server, and spite or not, you realize that I’m pretty good at it, I can do any one of these things and more.

And as always, my rates are reasonable. So, you should hire me for these things because I’m better than everyone else at them. If you need low cost, very high quality training for SQL Server, I have things from beginner to intermediate to expert. There’s 24 hours of it and you can get it for 75% off for life, which means about $150.

And you can, you know, depending on how long you live, that could be a great deal. If you want to see me live and in person, if you want to see my, my, my human presence, my corporeal form, appear on a stage in front of a laptop and talk about all things having to do with SQL Server performance, you can catch both Kendra Little and I for two days of performance tuning magic and miracles and witchcraft and all sorts of other.

We’re not going to kill a goat, don’t worry. But that’s November 4th and 5th at Pass Data Summit in Seattle. If there’s an event near you that is in need of a pre-con speaker and you think, hey, I bet Erik Darling would like to come here and pre-con this, this, this thing.

Let me know what that is, because there’s a pretty good chance that it’ll show up. Who knows? Right? That’s the worst that could happen.

And with that out of the way, let us continue our party extravaganza with SQL Server and its inconsistencies. All right. So here’s the setup. I have a table called transaction test.

And I’m going to start just by clearing the whole thing out. And I’m going to put in three rows with just default values. There’s a check, there are a couple of things on this table. There’s a clustered primary key on the table, which wonderful.

We should, we should have those for our, you know, transactional tables. Look at this great cluster of index. There’s also a check constraint on this table. It just checks to make sure whatever data goes in there is greater than or equal to get date.

Okay. So that’s, that’s the only thing that we care about in this one. So if we look at what’s in that table now, we will see that there are three rows with this dead giveaway date to let you know that I’m indeed a dork recording on a Saturday. God help.

So, and that’s about it there, right? Nothing too weird, wild, crazy out of the ordinary. Here’s what I’m going to do. I’m going to set no count. Well, actually, I’m going to set no count on and I’m going to set exact abort off, which is a worst practice.

Usually for anything like this, you would want to set exact abort on to avoid exactly this kind of strange oddity, this insane confusion. But I’m going to, I’m turning it off to show you what will happen if you do things in the worst possible way. You want to do things in the best possible way, which is to turn exact abort on if you’re going to do stuff like this.

If you’re not going to do stuff like this, I don’t care what you do. You can, I don’t know, do whatever you want. So we’re going to begin a transaction and we are going to make sure that set exact abort is off or not on, right?

We’re going to go like the is not trusted foreign key, right? Set exact abort is not on. And then we’re going to update this table, which is totally fine.

That’s going to work. It’s in the transaction. We’re going to select from the table and that’s going to look right because we have incremented this to the first when rent is due and why you should subscribe and hire me and buy training because rent is due people. Rent is due.

And now we’re going to delete row number two from the table and that all goes fine. And this is going to look right. And that’s also going to look right now too, right? That row number two is gone.

We have gotten, we have deleted row number two. And now we’re going to do this. And this is going to fail because we are violating that check constraint that we put on the table, right? We can see all of this stuff happened here.

And the important thing that I want you to pay attention to is at the end of all that red text, there’s a little line here that says the statement has been terminated. Right? That statement was terminated because of the check constraint and keep that in mind for later.

So now what the table looks like is exactly what it looked like before. Row number three is, well, the row number two is gone. Row number one is a day ahead, but nothing happened to row number three because that update failed.

All right. We’re going to commit that. And then before we go back up to the top of the script, we’re going to do a little switcheroo here. And we’re going to instead on the second time through, rather than just rather than violate that check constraint, we’re going to have a conversion error.

Right? So we’re going to try to set the date column to something that is absolutely not a date. Right?

Okay. So let’s start over again. Let’s rebuild our table. Right? And actually, before we do that, just to make sure that we don’t have anything open that’s going to cause anything weird, we’re going to make sure that transaction is extra committed. Because, Lord help, if I mess up on the second time through because I didn’t make sure that that was done.

Bush League. You’ll probably see that in someone else’s training, not in mine, though. So I’ve basically recreated the table.

I put all the same stuff in there. And we’re going to follow the script in the same way. We’re going to set no countdown and we’re going to keep XactiBort off. Again, a worst practice.

An absolute worst practice. You don’t do this in your queries. You’ll be mad later if you don’t. And then we’re going to begin a transaction. And then we’re going to just double check to make sure XactiBort definitely not on.

Definitely a bad idea. Definitely not a good idea to have XactiBort off. XactiBort not on.

Right? So we’re going to go through. We’re going to step through. We’re going to update that. And we’re going to say, hey, what’s in there? And that’s correct now, right? So that’s 901. So that got moved forward.

Great, great, great. We’re going to delete row number two. And that’s going to be absolutely fantastic. And we’re going to select now and look at this. And we’re going to say, yep, row number two is gone.

We have passed all these tests. But now we’re going to do this. And what we have here is SQL Server just says, conversion failed when converting date and or time from character string. Now remember, when we violated the check constraint, there was a little piece of blackish, grayish, not red text under there that said, statement has been terminated.

We don’t have that on this one, do we? SQL Server doesn’t say anything additional here. It just says there was an error.

Sorry. Yeah, that’s crazy. But now look at what happens when we run this. All three rows are there and all of them are back to their original data.

So this one never changed because the conversion failed. This one got deleted, but is now back. And this one no longer has the date of 0901.

This one has a date of 831. So the update that pushed that forward a day also got rolled back. Now, to me, before we do that, the reason why this error message is in here is because if I try to run either one of these, this one will say, the commit transaction request has no corresponding begin transaction.

So that means our transaction got killed and completely rolled back. So like, of course, I can’t roll it back either, right? So this one here says the rollback transaction request has no corresponding begin transaction, which is why I have this sort of slash thing in here.

In real life, you would never see the commit slash rollback. It would just say one or the other like I just showed you. But that’s why that’s there in shorthand because that’s what you get from either one of those.

So just to sort of reiterate, all the rows got rolled back to their original state because we hit a conversion error instead of just like some other kind of error. Now, the full list of errors that will cause a batch to completely roll back if you hit them is not really documented anywhere. The closest that I’ve found is in Erland’s article.

So, where was that hiding? There we go. There’s not, it’s kind of hard to find your way around in these things sometimes. So, there are some things that Erland has brought up here.

And one of them is most conversion errors, which is this line right here. I don’t know what most means. I don’t know if there’s a conversion error that would not cause that to happen.

But I do see that most conversion errors would cause that to happen. Which is pretty wild. I also think that this one is pretty wild.

Arithmetic domain error is like squirt on a negative number. Why would you do that? It’s mean. So, this is sort of like the closest I can find to like an actual list.

There might be a longer list out there somewhere, probably in like SQL Server source code, that isn’t available to human beings. But we can get a partial list of that stuff here.

But anyway, the reason why this came up is because I was trying to do a simple demo that showed, you know, exactly kind of what I was trying to show when the check constraint got violated. Except I, you know, without, sort of without thinking much about it, I was just like, oh, I’ll just do the, this is not a date excel.

It’ll be funny. Everyone will laugh. But it turns out, I outfoxed myself on that one. And I thought I was crazy. And I was like, why is this thing doing this?

And of course, I had to revisit Good Sir Erland’s article. And that section was in there. And that at least partially explained my problem. It’s just something that I totally kind of forgot about after, you know, looking at that article a billion times.

So it can happen to anyone. But the important thing here is not that I’m forgetful or not that I did something silly and foolish and now I decided to maybe pass something along to you that will help you down the line in the future if you’re ever doing this stuff.

My, my, the, the really, the, the big takeaway from this video is that if you are going to begin a transaction and you are going to run multiple modification queries in that transaction and you are going to do that in some facility like a script or a store procedure or really anything, like anywhere you’re going to run this sort of thing.

Please set exact abort on. Do not use exact abort off. If you set exact abort off, you’re at this sort of undocumented women fancy of stuff like that conversion error causing the whole thing to roll back, but other errors causing only the one thing to not roll, not roll, not commit, right?

So the other things stayed, right? The other changes were there. Just that one thing failed when it was the check constraint violation.

When it was a conversion error, whoo, it was awful. It was, it rolled the whole batch back and killed the transaction. So different errors will, are handled differently in SQL Server with a lot of inconsistency.

And the only way you can protect yourself from that is to say set exact abort on, on, on, not on, on. There we go. I found the right key. So if you want to protect yourself from foolishness, idiocy, inconsistencies, and all other slings and arrows, barbs and, I don’t know, spears and nunchucks and other things that would, that would really hurt in SQL Server, you should, you should always set exact abort on for stored procedures that you care about.

So, I don’t know. That’s that. And since it’s Saturday, and so are we, I’m going to finish this recording and I’m going to go do something with my family, because, um, I, I, I, I suppose, I suppose they, they might want to see me today.

I suppose you might not be the only people who want to see me today. Or you might, you might just be spitefully watching this and mashing your teeth and clobbering your fists together and… I don’t know.

I really, I don’t, I don’t understand your motivations, to be honest. Anyway, uh, thank you for watching. I hope you enjoyed yourselves. I hope you learned something. I hope that, uh, you will do all the things that give me money.

Uh, and, uh, what’s the other one? Uh, well, yeah, I, I suppose thank you is in order since you did, you did make it to this point. So, uh, if you, if you are still watching, thank you for watching.

And I hope, I hope you’re having a great weekend. Or you had a great weekend when this weekend was. Next weekend, I’m going to be in Dallas. But this video is not going to get published until way after that.

Because I, I, I make a lot of content. So, anyway. Off to the mines. 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.

A Little About Index Intersection Query Plans In SQL Server

A Little About Index Intersection Query Plans In SQL Server



Thanks for watching!

Video Summary

In this video, I delve into the fascinating world of index intersection plans with Erik Darling from Da Darling Da Data. We explore how these plans come to life when your table has multiple indexes and SQL Server decides to use a combination of them in a unique way. Unlike simple index unions where data streams are concatenated, index intersections involve joining those indexes together to produce the result set. I also share some practical advice on managing indexes effectively—avoiding unnecessary single key column indexes and opting for compound keys with included columns for better performance. Plus, if you’re curious about upcoming events or need personalized SQL Server support, I discuss how my services can help. Don’t forget to join our community by subscribing, liking, and commenting!

Full Transcript

Erik Darling here with Da Darling Da Data. All those Ds. Deepen the Ds there. In today’s video, we’re going to talk about, drumroll please, index intersection plans. No jokes about me not having a driver’s license, please. These are a type of query plan that you will see when your, you know, table has multiple indexes on it and SQL Server chooses to use some combination of those indexes. But it’s a little bit different from the index union thing because rather than just concatenating the two streams of data, we do something a little bit different and we’re going to talk about that in a couple of seconds as soon as we talk about how you can buy things from me. If you would like a membership to this channel, four bucks a month, not bad. That’s like, I don’t know, with inflation, that’s like quarter of a box of mac and cheese. Right? If you would rather have a quarter of a box of mac and cheese, I totally understand. So would my kids. Then you can do all sorts of other fun things that help you, that help me, that help you help me, like like and comment and subscribe because that’s nice things to do. If you need help with SQL Server problems, because I am a SQL Server problem fixer, I am available to do all of these things and more and my rates are reasonable.

If you would like some reasonable rates on high quality SQL Server performance tuning that will last you a literal lifetime, you’ll never have to subscribe to this chunk of training. You can get it for about 150 US dollars with this discount code. This link is also in the video description. Just like the membership thing and just like Intel timing out being able to find driver updates constantly in the background. That thing shows up like every 45 seconds. If you would like to see me live and in person without Intel driver updates timing out in the background, you can actually they might still show up because I have to use this laptop for those things too. You can catch me at past data summit with Kendra little doing not one but two days of SQL Server performance tuning magic, November 4th and 5th in Seattle, Washington.

And that’s that’s the last event that I have so far for 2024. 2024. Gosh 2025 sure did sneak up quick didn’t it. Golly feel like I just paid taxes. You can tell me if there’s an event near you that you think I might be a good addition to and if they are looking for pre con speakers, there’s a chance I’ll even show up looking pretty.

You never know. Crazy things, crazy things happen in this wild data world of ours. But with that out of the way, let’s talk a little bit about index intersection. Now, just like in the index union video, I have two single key column indexes.

And just like in the index union video, two single column key two single key column indexes are not strictly necessary for this to happen. And it’s just easier for me to do this way. I have to type less and I have to work less hard on the demo part of it, which means that I can record it and show you stuff better.

So that’s great for everyone. So in this first query right here, we are going to run a query that says where creation date is greater than this and last activity date is less than this. And the resulting query plan will miraculously use index intersection.

You can see that right here where we seek into this index and we seek into this index. But then instead, like in the index union plan, there was this concatenation operator. There was a regular concatenation and there was merge concatenation.

But now instead of concatenating, we actually join those two indexes together to get our results set. So we take all the rows from this one and we join all the rows from this one. How do we join those rows, though?

Well, it’s just like with a key lookup. SQL Server has the clustered primary key for the post table stored away as a hidden key column in both of those nonclustered indexes. Because they are not unique indexes, the clustered index key column, in this case singular, is an additional key column hidden away in there.

If this index were unique, the clustered index column would be treated like an included column. So SQL Server uses the ID column to join those two indexes together here. Just like with the key lookup, it would use the clustered index key column or columns to get data out of the nonclustered index and then seek into the clustered index to locate the additional rows that we need to get the additional columns that we need out.

So it’s almost the same principle, just with a slightly different join setup. Rather than a nested loops join, we have a hash join. And I don’t know, I guess SQL Server just felt strongly about that.

You might see other join types in there. Like you could see a merge join in there because I have put a merge join hint on this plan. And golly and gosh, doesn’t that just work out in our favor? But you can see why SQL Server did not choose the merge join plan naturally for the other query, which other than the merge join hint was identical.

Because it would have to fully sort both of those result sets in order to use the merge join. Now, you totally could see this in real life if SQL Server costed the hash join out of proportion to the two sorts in a merge join. Or if you had equality predicates on the two date columns, but two date-time columns rather.

But come on, who uses equality predicates on date-time columns? It’s obsessively difficult. Like, bleh!

I don’t know what would be wrong with you to do that. You would have to so very specifically be looking for something. Or a date column, maybe, but holy cow. Now, you might see SQL Server choose different join types.

So here, because I said it when I had to do it, we are going to use two equality predicates here. We’re not going to find any rows, and that’s okay. But here’s an example of where a merge join plan happens somewhat more naturally, because we don’t have to sort that input.

The reason why we don’t have to sort the input is because we are using two equality predicates. And if you’ve watched some of my other videos on indexes and how indexes work, you’ll already know that when you have equality predicates, like an equality… So in this case, we have an equality predicate on creation date, which means that the order of the ID column will be preserved.

And the query up above that, where we had a range predicate or an inequality predicate, greater than, equal to, less than, less than, equal to, all that stuff… That ordering is not preserved in the index. So that’s why it would have required sorting up there, but not down here with the equality predicates.

And just like with the index union plan, SQL Server is able to do index intersection plus a key lookup. So to imitate the index union demos a little bit, we are going to select the post type ID column and group by the post type ID column. And of course, since the post type ID column is not a clustered index key column, it is not going to be present hidden anywhere in either of these nonclustered indexes.

There is no invisible post type ID. So when we run this query and we get the resulting query plan, even though nothing comes back, SQL Server still faithfully executes the query. Does a seek into this index?

Does a seek into this index? Uses a hash join once again to join those two indexes together on the ID column, which is the clustered primary key. And then we have a key lookup back to the post table to get that post type ID column.

And just to sort of bring the point home that I was making before, we are outputting post type ID from the lookup. And our seek predicate is the ID column because that is the clustered primary key of the post table. That’s the column that we use in the lookup to locate the correct row for the columns that we need.

Pretty cool, right? So again, single key column indexes, generally not the first thing that I recommend for SQL Server. You can end up with some tremendously weird, complicated, probably ill-performing query plans.

If you are the type of person who creates a single key column index on every single column in the table, not usually a good idea. Compound index keys with included columns are usually the best strategy for either OLTP or, you know, reporting type workloads or rather mixed workloads. You know, there’s always the columnstore question, but, you know, that one’s a little bit too much to answer.

And a video that’s not about the columnstore index question. So we’re not going to get into that. But we are going to say thank you for watching. I hope you enjoyed yourselves.

And I hope you learned something. And I hope you will stick around and watch more videos because I have many, many more to come. Many such videos. Who knows what the next one will be about? It might even be about something crazy like doping bitmaps.

You might even put money on such a thing. If there is like a betting channel for me and what my next video is going to be about, I would heavily suggest voting on .bitmaps. I would not suggest voting on join or clause plan patterns.

I’m not saying that because I’m going to switch the order of those and make you lose all your money. I would much rather have you spend your money on me in other ways. Anyway, thank you for watching and I will see you over in the next video.

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.

Join Me And @Kendra_Little At @PASSDataSummit For 2 Days Of SQL Server Performance Tuning Precons!

Last Year


Kendra and I both taught solo precons, and got to talking about how much easier it is to manage large crowds when you have a little helper with you, and decided to submit two precons this year that we’d co-present.

Amazingly, they both got accepted. Cheers and applause. So this year, we’ll be double-teaming Monday and Tuesday with a couple pretty cool precons.

You can register for PASS Summit here, taking place live and in-person November 4-8 in Seattle.

Here are the details!

Day One: A Practical Guide to Performance Tuning Internals


Whether you’re aiming to be the next great query tuning wizard or you simply need to tackle tough business problems at work, you need to understand what makes a workload run fast– and especially what makes it run slowly.

Erik Darling and Kendra Little will show you the practical way forward, and will introduce you to the internal subsystems of SQL Server with a practical guide to their capabilities, weaknesses, and most importantly what you need to know to troubleshoot them as a developer or DBA.

They’ll teach you how to use your understanding of the database engine, the storage engine, and the query optimizer to analyze problems and identify what is a nothingburger best practice and what changes will pay off with measurable improvements.

With a blend of bad jokes, expertise, and proven strategies, Erik and Kendra will set you up with practical skills and a clear understanding of how to apply these lessons to see immediate improvements in your own environments.

Day Two: Query Quest: Conquer SQL Server Performance Monsters


Picture this: a day crammed with fun, fascinating demonstrations for SQL Server and Azure SQL.

This isn’t your typical training day; this session follows the mantra of “learning by doing,” with a good dose of the unexpected. Think of this as a SQL Server video game, where Erik Darling and Kendra Little guide you through levels of weird query monsters and performance tuning obstacles.

By the time we reach the final boss, you’ll have developed an appetite for exploring the unknown and leveled up your confidence to tackle even the most daunting of database dilemmas.

It’s SQL Server, but not as you know it—more fun, more fascinating, and more scalable than you thought possible.

Going Further


We’re both really excited to deliver these, and have BIG PLANS to have these sessions build on each other so folks who attend both days have a real sense of continuity.

Of course, you’re welcome to pick and choose, but who’d wanna miss out on either of these with accolades like this?

twitter
pretty, pretty, pretty, pretty good

You can register for PASS Summit here, taking place live and in-person November 4-8 in Seattle.

See you there!

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. I’m also available for consulting if you just don’t have time for that, and need to solve database performance problems quickly. Want a quick sanity check before committing to a full engagement? Schedule a call — no commitment required.