Live SQL Server Q&A!

ICYMI


Last week’s thrilling, stunning, flawless episode of whatever-you-wanna-call-it.

Video Summary

In this video, I delve into some fundamental SQL Server and database design principles. Starting off, I discuss the importance of avoiding wide tables—tables with more than 100 columns—and how they can lead to indexing challenges and maintenance headaches. I also share a few patterns to look out for in your schema that might indicate poorly designed tables, such as prefixing column names or using numeric suffixes for similar data. Additionally, I explore the concept of Michael J. Swartz’s 10% rule, emphasizing that while SQL Server supports up to 1,024 columns per table, it’s wise to keep your tables narrow and well-structured.

Moving on, I cover memory grants in queries, explaining how they are necessary for operations like sorting and hashing but not always required. I provide practical examples of scenarios where a query can process terabytes of data without needing any memory grant, highlighting the importance of understanding these nuances for optimizing performance. Throughout the video, I offer actionable advice on improving your database design, referencing Lewis Davidson’s book “Relational Database Design for SQL Server” as an excellent resource to guide developers in creating more efficient and maintainable databases.

Full Transcript

So, let’s see. Science and technology is the, I don’t know, channel, stream that this thing takes place in. I don’t know if that’s really accurate. I don’t know if I’d call it science or technology. Maybe, maybe neither. It’s not terribly scientific. And aside from the fact that I use a computer to broadcast, I have absolutely no technology in here. I did get a new phone. Got a Pixel 3 XL. Pretty psyched about that. It’s a picture of my kid picking her nose on there. She’s a good time. So, just to kill a little time until questions come in, I see people in here, which is very exciting. Thank you for joining me. I’ve been writing some blog posts about my first six months of consulting independently. And, I don’t know, it was a real tear-jerker. There were some emotional moments in there. I’m kidding.

I replaced feelings with whiskey many decades ago. But, anyway, I’ve been, shut up, motorcycle. Back to your biker gang. I’ve been getting allergy shots for, I want to say, like, three years. At least three years. I’m on, like, the monthly maintenance shots now where, like, I just, like, they don’t even make me safe for a half hour anymore. I get my shot and I hang out for, like, 10, 15 and just say goodbye. Whiskey. Whiskey. Tisigr asks, whiskey or bourbon? I would never drink bourbon. Bourbon is just hyped up maple syrup. I would never drink bourbon. I’m a scotch guy.

And, like, very specifically, I like Iowa scotches. I like the stuff that tastes like burning band-aids. That’s my jam. But, yeah, we’ve been getting allergy shots for three years now, monthly maintenance. And the last, I don’t know what changed in the, I don’t know what changed in the world. I don’t know what new life form showed up on planet Earth. But it’s, like, I’ve never had a shot in my life. For, like, a long time, they were great.

I, like, I went from having, like, severe, like, eyes running, nose constantly bubbling, gross stuff, allergies, like, unable to function allergies, to, like, I would take maybe, like, antihistamine, like, once a week or so or, like, once every couple weeks. It was great. But, man, the last week has been absolutely positively brutal. Like, I’ve been taking stuff every day. Like, every morning, I wake up at, like, 3, 4 in the morning with, like, my face in awful condition.

I don’t know what the hell is different this year. But, man, it is bad. Bad. Ugh. It’s terrible.

Anyway, I don’t know. I don’t know. Someone ask a question. There are at least six of you here.

I don’t know how many more are going to come in. There are some of you. Someone has to have a SQL Server question. You can’t just come here to hear me complain about things or blab on and on about blog posts and whatnot.

Someone has to have a question about SQL Server. Please, God, someone have a question. Come on, there’s, like, 10 of you. Laura says, do you have any thoughts on replacing temp tables with in-memory OLTP?

Yeah, don’t. Hecaton is like herpes. It doesn’t have the nerve to kill you.

It just hangs around being awful for the rest of your life and flaring up at inappropriate times. So, I just have not found anything compelling about Hecaton. It seems like every time…

And this is not just me. This is some very smart people that I’m friends with. Every time they think that they have a specific problem with latches that Hecaton might solve, it is an absolute dead end.

Absolute dead end. In SQL Server 2019, TempDB is going to use… Well, you can use TempDB as, like, an Hecaton-y thing anyway.

I would pry this whole lot to see if that happens. But, whereas, I guess the question is, why? Why?

What are you trying to fix? What is a problem that we’re trying to solve here? What do you think Hecaton is going to make better in your stored procedure that you want to use in memory for?

That’s the big question. What’s going to get better about it? What’s going to get better? I don’t know.

Do, do. Do, do. Do, do. Try to have better indexing of… What on earth? You can index TempTables now.

What’s missing from your current TempTable indexing that Hecaton is going to provide a safe and secure solution for? That’s what I’m curious about.

Too slow. Well, so here’s the thing. What’s too slow? Creating the index or populating the Temp…

or populating the table? Josh says Hecaton indexes are extra confusing. Yeah, they are. So who knows how many hash buckets you should set up? Creating the index.

So… Creating the index is too slow. Okay. Fair enough. See, this is one of those things where it’s like, we’re going to end up going down a real rabbit hole.

Because I’m going to ask you what kind of index. And what kind of data types you’re indexing. And how many rows it is. And a lot of other stuff.

And this is… I hope you’re prepared. Because this can go on for a long, long time. I know.

Then you’d have to actually look at code. Bad news is you’d actually have to look at code to implement Hecaton. and get it set up and running there. If you don’t…

If creating an index is slow now on a Temp table, I don’t think it’s going to be any faster in Hecaton. It’s not free.

Nothing is free. Nothing is free. Yeah, no. Well, you know, my…

So Hecaton is very specifically designed to deal with locking and lashing issues. The in-memory portion, I think, is…

The misgiving around it is that it’ll make any workload faster. And that’s not really true. And the use cases where I’ve seen Hecaton be successful is with really large-scale, fast data ingestion into tables where data is not going to live for very long.

So the example that I always give, because it’s the example that I’ve seen work best, was with online gambling, where data had to come in very fast. And we cared about that data for a very short amount of time, where we wanted very fast updates and being able to get to that data to be snappy and not get blocked up and locked.

And then after like an hour or so, or whenever the betting thing is over, they get pushed out to regular on this table. So that’s the only time.

It’s the only time I’ve ever seen Hecaton be successful. For every other weird niche thing that someone’s like, oh, I bet if we just did this in memory, it’d be faster.

It has never worked out. Never worked out. I don’t know. It’s like, people hear about these features and they, I don’t know, the pamphlets that Microsoft comes up with for these things are amazing because they make it seem like they’re going to fix every single problem that you’re having.

They just sound like this, like golden acres retirement home for your data. And geez, never, never seems to go.

Yeah. So like with the Lars, I guess to put it, put it as succinctly as possible. you can try it, but be very careful with the database that you try it in because once you have created an in-memory file group in a database, one cannot drop an in-memory file group.

One must drop and recreate the database. So if you’re going to try it somewhere, create a new database and enable things there. Don’t do it with a database you care about.

So that’s about all I got. Let’s see. TZH X says, do you have a rule of thumb for how wide a table is too wide? Currently have a bunch of core tables in the system with a hundred plus columns each.

Yeah. You’re about there. So I think one way to, one way to phrase this really well is the maximum number of columns you can have in a table is a thousand and 24.

And a really smart friend of mine, Canadian fella, says it has, has sort of a, what do you call is it? Michael’s Michael J. Swartz 10% rule with, if you’re using 10% of a maximum of a limit, right?

Cause these are limits. They are not goals. They’re not things that you are trying to attain. They are things that they are, they are like, like the, the capacity at which SQL Server will stop functioning. So you’re, you’re right about there.

And the thing that really sucks about a hundred column tables is they’re really, really difficult to index. Well, unless you don’t care about read speeds from them, unless you’re just dumping data in there and not really doing anything with it, then it becomes really hard to index that.

Cause people are, people are going to you know, they’re going to want to search on, on weird sets of columns. And they’re going to want to select weird sets of columns and potentially order by weird sets of columns.

And all that adds up to is a lot of a heartache and pain and trying to index those things. So when I start seeing tables like that, that there are three patterns that I look out for very specifically.

one is column names that have prefixes. So like customer name, customer number, customer address, customer, things like that. Cause those should all be in a table called customer. The other thing that I look out for is, uh, columns that end in numbers.

So like phone one, phone two, phone three, email one, email two, email three. I’m also, I also pay attention to those. Cause those should probably be in their own sort of like entity, attribute value type table, like a, like a long narrow table where, you know, you link up, you know, like, you know, different, different, uh, entities to different attributes and different values than EAV table.

It’s amazing how that works out. The third thing that I look out for when I see very wide tables is, uh, clusters of missing index requests.

So like, like a lot of the times when I’ve seen crazy tables like that, um, what’s, what’s jumped out to me is that, um, missing index requests will be very specific around, which columns are in them.

So the, so like you’ll have a very similar set of search columns and a very similar set of select columns. And oftentimes you can find ways to break, to normalize that table, to break that table apart into separate tables based on what people are searching on within that table.

So you might find like, you know, setting, let’s say, like say you find 12 missing indexes and out of those 12, like four of them are selecting columns one, two, and three.

And the where it causes on columns four or five and six. And then, and the, another one, there’s like four requests and now there’s some, you know, sort of tanglement between columns eight, nine and 10 and 11, 12 and 13.

So like, you can usually find patterns within the missing index requests and within the column names, they can give you some pretty good direction and breaking. I keep saying breaking. I mean, normalizing.

It don’t mean you’re breaking the table. You’re not hurting the table. You’re not breaking your SQL Server. You’re normalizing your data, which is a wonderful thing to do. If you want more advice, if you want a lot of really good advice on doing that, Lewis Davidson has a book called relational SQL Server database design or something like that.

Let me go, let me go over to Amazon and grab a link for it. Cause it’s, I’m probably, I’m probably getting the title or something wrong.

I wish Davidson equal server design. It’s a book that I have in my bookshelf too. It’s not like I just tell people to buy these things and then screw off and let you do all the hard work.

It’s a book that I actually own and I’ve actually read. So let me stick that link in there. There you go. Wonderful.

We have a link. That’s beautiful. That’s a good thing. And let me actually, while I’m doing that, Michael, I should spell Michael, J sport 10% rule. There we go.

Swartz 10% rule. So I would send this stuff over to your developer. I would, I would get a copy of that book for your developers. And I would, I mean, unless you’re the developer, in which case I’m sorry for making money, but you know, could sell, send them, send them, show them that book, show them that link.

Say maybe, Hey, pal, we need to fix this. We need to do better. We need to do better and be better at our SQL Server, relational database design and implementation.

It’s, it’s crazy. So like, I’m like around the five year mark of consulting. I mean, obviously not independent, like, like just sort of generally like general consulting.

And I’m going to say something, uh, that is, is very, okay. I think it’s going to be annoying to a lot of people.

And as your problems are not special, your problems are very fundamental. the problems that your database is having is because you did something weird and wrong. You embraced wide tables.

You did not embrace clustered indexes. You embrace scale, our functions. You, uh, you know, did not pick a decent indexing strategy. There are so many just basic, like you, you chose data types really poorly.

Everything is a, is a long string for some reason. Everything is, uh, mistyped across tables. This, it’s very often, just very fundamental, easy fix things that like you, like should have been done from the get go, but just weren’t.

And, and it screws everything up. And it, it breaks my heart to come in and see like the same problems over and over again, where it’s just like you, like if someone had just made a couple better decisions at the outset, you could have avoided years of problems.

Years of problems. Let’s see. Kapil asks, can a query have a long running query on terabytes of database? Zero granted query memory.

I see some on large queries and don’t get why. Yes. Uh, so query memory grants. So like every query gets some memory. Because every operator.

Requires some memory to like figure out what state it’s in and what it’s doing and what it’s up to. So that every query gets some memory memory grants are very specific to a couple operations. One of them is sorts, right?

So if you sort data, you’re going to require a memory grant to do that. Uh, if you hash data, whether it’s a hash match aggregate or a hash join, you’re going to require memory to do that. And if your query requires, or if your query goes parallel, you’ll require a little extra memory to manage the exchange, the, the parallel exchanges, the buffers and the parallel exchanges.

So there are, there are three things that base, uh, in a nutshell require memory. There are some less, uh, frequent things like optimized nested loops join, which also will ask for a memory grant. And there’s, using like doing like inserts, the columnstore will also ask for a memory grant, but, uh, yeah, it’s, it’s entirely possible to have a query run across terabytes of data and not have to sort or hash anything.

It’s entirely possible for that to happen. And for a query, not to ask for a memory grant to do any of that work.

So fun stuff, huh? Like, like it’s, it might not be a great query. It might be a very slow, terrible query because at that point, I’m picturing a query with like, like lots of like little nested loops joins and, or maybe like in like index supported merge joins where, uh, memory just isn’t required to do any of that stuff.

So yeah, it’s totally possible. Uh, I find it a little suspicious that, that it’s happening on terabytes of data. Cause usually when you get terabytes of data involved, SQL Server is, uh, is pretty keen on doing some hash joins there, but who knows, who knows if you have a query plan that you can share and that you have a question about, uh, that would be a good question for DBA dot stack exchange.

Dot com. That’d be a wonderful place to ask a longer version of that, where perhaps you could provide some more detail and people could give you much more detailed answers. But I think in a nutshell, that should, that should get you going in the right direction.

Let’s see here. TZH asks, I’ve got a handful of nonclustered indexes on the clustered index key with separate columns included to cover some repeat queries. It just seemed dirty.

Yeah. I mean, you’re between a rock and a hard place, right? It’s either you, you, you got these wide tables. And if you don’t index them, uh, people are going to complain and it’s hard to index them. And if you over index them, people are going to complain.

And, uh, it all sucks. It all sucks. Uh, James says, how do I sign up to get the blogs you write sent to my email?

I added both my personal and work email to the newsletter, but never seemed to get any emails. I have checked junk mail and filters. Um, um, I don’t know what would, what would be going wrong there.

I, uh, that email list seems to work because, um, uh, what do you call it? I get, like, I see the email comes to me and I get bounce backs from everyone.

Who’s like, I get everyone’s out of office reply. Cause I’m an idiot and I don’t know how to change that in MailChimp. And when I think about Googling it, I find something better to do.

So I, but it’s, it’s kind of nice to see it’s working. It’s like, it’s like my way of knowing that I’m not just like alone in my office for eternity. Uh, but yeah, uh, I’m not sure if you shoot, if you send me an email, like if you use the contact form on my site to send me an email, um, I can, I can, I can look at your email address and look in MailChimp and kind of see what’s happening there.

It’s entirely possible that like, for some reason, I don’t know, maybe you spelled your email address wrong or, uh, or, or maybe you’re like, whatever your, your email host is just, just hates stuff from MailChimp so badly that it doesn’t even make it to spam, but just gets like immediately quarantined and junked.

It’s also entirely possible that I’ve been blacklisted again, because I don’t know. It’s happened before. I got an SSL certificate and I thought things were cool and people are still like, yeah, we don’t trust you.

I’m like, I got a certificate. I paid GoDaddy like 125 bucks for this damn certificate. How can I be untrustworthy? How, how is, how tell me how that works? Very annoying.

I don’t know. Anyway, Forrest, my furry friend. Oh, by the way, I meant to say if, if you sent me your address last week to get stickers mailed out to you, stickers were put in the mail, uh, this week.

So you should be getting them eventually. I don’t know when. Be honest with you. I wish I could, I wish I could predict and control the mail. Unfortunately, that is where my powers run out.

Anyway, Forrest says, or asks, do you ever find that knowing about memory management internals is helpful? It’s helpful when I create very specific problems that deal with memory management.

I would say that for the general public who have a pretty well-defined set of SQL Server issues, memory management is almost never, um, the issue.

The issue is that SQL Server is managing like 12 gigs of memory when it needs like 128 gigs of memory. Uh, so, you know, it’s, it’s good stuff to know.

It’s good. Like, like SQL jeopardy stuff to know. There’s a lot of good SQL jeopardy stuff to know, but you got to keep stuff, that stuff like back here, the stuff you got to keep up here is way different. Um, so yeah, it’s, it’s good to know.

It’s nice to know about, but, uh, whether it’s ever, whether I’ve ever like walked into a customer site and been like, uh, ha, I see you’ve got stolen pages. Let’s solve that.

Let’s crack that caper. And it’s been like some weird problem. It’s never been a weird problem. It’s always been like, well, you, you, Oh, you put like 500 gigs of, of memory inside SQL Server, but you forgot to raise max server memory from 64 gigs.

When you made the change or like, you’ve got a SQL Server, you are like, you’ve got a VM host with like eight SQL servers on it. And they’re all just fighting over memory constantly.

Or like, just like, like, like stuff has never been like, like, Oh, SQL services bad managing memory. It’s always been like, Oh no, there’s a people problem.

We have a big people problem here. So there’s that. Kapil says, perfect. I got it. Yes, indeed. It’s one heck of a crappy query. Too many inner joins with no sorting in a serial plan.

Woo. We, so wow. Serial plan. So we have a serial plan with no sorting over terabytes. Is there a scalar valued function involved here or a table variable involved here? Because there, there is something amok when we have a query that looks like that over that much data.

And, and there’s like no memory grant or sorting or anything that something stinks about that. Something is stinky in there.

Something is very, very stinky in there. Kapil falls. Are you planning to release some training videos? I learned a lot from, you’re available at Brent’s query tuning classes.

Yes, I am. So I’m in, I’m in a weird, weird position. And I wrote about my weird position a little bit in, in my, in my six months of consulting on my own blog posts, which will be out.

I don’t know, but I think on the anniversary of, on the anniversary of me getting laid off. So it’ll be like June 3rd, which is exactly six months from January 3rd, which is when I got beheaded.

Or when I, when I, when I had a fall from grace and became unfamous or whatever, whatever you want to call it. But yeah, so it’s, it’s been a tough mix of, you know, trying to write good training material.

In other words, like from scratch. So it’s like, I, I do like, well, I’m not, I will never ever be accused of being accused of being a perfectionist. I, I do like to provide high quality or as high quality as I’m capable of material with, with the training.

So I do try to like, you know, have, have nice pictures and have things be laid out clearly and look nice. And, and the other thing is that I don’t want to just repeat myself. So I wouldn’t want to just like completely rehash training videos that I had recorded that are up on Brent’s site.

If I’m going to, if I’m going to do this thing, it’s got to be me. So it’s, it’s tough because now I have to sort of find new ways to say things or new ways to present things.

Uh, so it is, it is writing all the material from scratch and, and it’s, it’s been going a bit more slowly than, than I’d like just because, you know, I mean, it’s a great problem to have because I, I have enough like consulting work coming in where it takes serious chunk, serious bites out of my, my time during the week where I can’t sit and dedicate it to, to writing the training, but it’s getting written.

Um, you know, I’m going to, I’m going to take, uh, some of the material from the, the, the server tuning pre-con that I’ve been doing. And, uh, I’m going to, that’s going to be like worked into some of the more advanced material.

Uh, a lot of the more advanced, uh, query tuning stuff is written right now. My, uh, my outline is, uh, like, like beginner stuff, uh, which I’m going to call starting SQL.

And that’s going to cover, um, that’s going to like do like, uh, sort of like a, like a jump, jump right into like, this is what a query does.

This is how, why it’s fast, why it’s slow. This is indexes, uh, you know, then going deeper into what is an index, what’s it, what’s a weight, you know, what’s a query plan. So like get, like getting like from like, like, like beginner stuff, but like, you know, not like, I think you’re a dummy beginner stuff.

Like I’m going to like teach you the really important, stuff about those things. Uh, then after that, I’ll probably do like a little bit of internals, not like, not like a book of boring, like, this is a database page.

This is a slaughter a type internals, like, like the stuff that I’ve found useful over the years. Um, then we’ll do like hardware stuff and then indexes more like more advanced index stuff. And then more advanced execution plan stuff.

And then query tuning. Um, I’m also hoping to have a very special guest, maybe do some, some stuff on columnstore. But I can’t say who or when, but that would be nice if that happened too.

So yeah, that’s my, that’s my plan. And, uh, and, and Josh to, to answer your email.

No, I don’t, I don’t have your address anymore. That was in my old email account. I don’t have access to any longer. Uh, so if you, if you, if you want to send me your, your actual address, that would be actually, I didn’t, I didn’t read enough of your email.

What the hell? What is this? I don’t even know what this is. Tastings. I’m not, I’m not going to you. You’re annoying me.

Email is terrible. email is the worst thing in the world. Darren says, he’s always found my classes, very informative and entertaining. Thank you, Darren.

I appreciate it. Uh, I’m glad someone does because most of the time, uh, when, whenever I’m talking to the camera, I’ll leave my office and my wife is staring at me like there’s something terribly wrong with me. I’m glad someone out there is, is, is entertained and informed.

The things that I say in here, otherwise, otherwise I don’t, I don’t know what I do. I don’t know. If, if you, if you, if you were one or the other, I would be, I would be ecstatic.

If you were informed or entertained by me, I would be thrilled. But the fact that you’re both, wow. I don’t, I don’t even know what to say to that. Enjoy, enjoy the stickers that I say to that.

Uh, yeah, that’s the thing. I don’t, I don’t carry over well to a lot of crowds. I have, I have a very specific set of people. I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I’ll tell you that that’s probably the hardest part about consulting is, is not like the consulting poker face or like, like the business stuff or the, like the, like the landing clients part.

It’s, it’s like the, the, how can I say this to like, how can I say this to people who I’ve never met in a way where like, I’m not gonna, I’m not gonna make everyone too angry.

And I think like, like that’s probably the hardest thing is, you know, like I, I need to say things in a way that they keep me interested in it. Entertain me.

But then at the same time, like I, I know someone’s not going to be happy with it. Like someone, someone’s going to be unhappy with it. Someone is going to be mad at me or, or, or think that I’m inappropriate or something.

So, ah, whatever. It’s a good time. You know what? Uh, if, if, if I had to pick a different way to make a living, I’m not sure what it would be.

Actually, I can’t, you know what I would do? I would open up a laundromat with a bar in it and I would only have it open from like, I don’t know, like 10 AM to like 8 PM, maybe 9 PM.

Cause I don’t want to like, I don’t want to have like an all night laundromat bar thing. I like, like, like, like, that’s like, like, like just washing machines full of pee would vomit would be the thing there. So I would open up a laundromat with a bar and I have very limited hours and I would just sit there and watch people do laundry.

That’s what I would do. Maybe I’d make a friendly conversation. I’d be the bartender, I guess.

I have a very limited drink menu. Cause I’m not very good at mixing things. Beer. The world doesn’t have enough shot in beer bars.

Everything’s a, everything’s a mixology bar. Everything’s going to have muddled, whatever in it. Shaved ice and rinds of things shot in a beer. Never did anyone wrong.

Anyway, uh, we’re at a, we’re at about a half hour here. And, um, uh, uh, uh, oh boy, this is a question. Hang on.

I just went through one of Joe’s blogs regarding soft Numa starting CWL server 2016. Per your experience, have you turned that feature off due to performance issues as raised by Joe in that blog? You should leave a comment on, on Joe’s blog post and ask him about it.

Cause Joe is by far a much, a much bigger expert on that Numa stuff than I am. So leave a comment with your question and, uh, mate, mate, and Joe will get back to you because he’s contractually obligated to is my guest.

No, I’m kidding. He’s not, he might ignore you, but if he does, I’m sorry. I’m sorry. I can’t control Joe. I was at first.

I was like, you know, if I’m going to do this, I should like, you know, say, okay, I’ll schedule your post to go out. And then I really just give Joe, let Joe off his leash, let Joe off his chain and do his Joe thing. Be ridiculous.

Trying to contain that man. Anyway, uh, that’s about a half hour. Uh, I’m going to, uh, I’m going to get going and, uh, get back to work or whatever you want to call it. Uh, thanks for joining me.

Uh, and I will see you most likely next week, unless something terrible happens. I’m kidding. Nothing. Unless I win the lottery, which would be wonderful. But if I do, if I win the lottery, I’m going to do this, uh, really drunk and tell you all what I actually think about you.

Goodbye. Have a great, great long weekend. if you are memorializing anything. Goodbye. Bye.

Bye. Bye. Bye. Thank you.

Video Summary

In this video, I delve into some fundamental SQL Server and database design principles. Starting off, I discuss the importance of avoiding wide tables—tables with more than 100 columns—and how they can lead to indexing challenges and maintenance headaches. I also share a few patterns to look out for in your schema that might indicate poorly designed tables, such as prefixing column names or using numeric suffixes for similar data. Additionally, I explore the concept of Michael J. Swartz’s 10% rule, emphasizing that while SQL Server supports up to 1,024 columns per table, it’s wise to keep your tables narrow and well-structured.

Moving on, I cover memory grants in queries, explaining how they are necessary for operations like sorting and hashing but not always required. I provide practical examples of scenarios where a query can process terabytes of data without needing any memory grant, highlighting the importance of understanding these nuances for optimizing performance. Throughout the video, I offer actionable advice on improving your database design, referencing Lewis Davidson’s book “Relational Database Design for SQL Server” as an excellent resource to guide developers in creating more efficient and maintainable databases.

Full Transcript

So, let’s see. Science and technology is the, I don’t know, channel, stream that this thing takes place in. I don’t know if that’s really accurate. I don’t know if I’d call it science or technology. Maybe, maybe neither. It’s not terribly scientific. And aside from the fact that I use a computer to broadcast, I have absolutely no technology in here. I did get a new phone. Got a Pixel 3 XL. Pretty psyched about that. It’s a picture of my kid picking her nose on there. She’s a good time. So, just to kill a little time until questions come in, I see people in here, which is very exciting. Thank you for joining me. I’ve been writing some blog posts about my first six months of consulting independently. And, I don’t know, it was a real tear-jerker. There were some emotional moments in there. I’m kidding.

I replaced feelings with whiskey many decades ago. But, anyway, I’ve been, shut up, motorcycle. Back to your biker gang. I’ve been getting allergy shots for, I want to say, like, three years. At least three years. I’m on, like, the monthly maintenance shots now where, like, I just, like, they don’t even make me safe for a half hour anymore. I get my shot and I hang out for, like, 10, 15 and just say goodbye. Whiskey. Whiskey. Tisigr asks, whiskey or bourbon? I would never drink bourbon. Bourbon is just hyped up maple syrup. I would never drink bourbon. I’m a scotch guy.

And, like, very specifically, I like Iowa scotches. I like the stuff that tastes like burning band-aids. That’s my jam. But, yeah, we’ve been getting allergy shots for three years now, monthly maintenance. And the last, I don’t know what changed in the, I don’t know what changed in the world. I don’t know what new life form showed up on planet Earth. But it’s, like, I’ve never had a shot in my life. For, like, a long time, they were great.

I, like, I went from having, like, severe, like, eyes running, nose constantly bubbling, gross stuff, allergies, like, unable to function allergies, to, like, I would take maybe, like, antihistamine, like, once a week or so or, like, once every couple weeks. It was great. But, man, the last week has been absolutely positively brutal. Like, I’ve been taking stuff every day. Like, every morning, I wake up at, like, 3, 4 in the morning with, like, my face in awful condition.

I don’t know what the hell is different this year. But, man, it is bad. Bad. Ugh. It’s terrible.

Anyway, I don’t know. I don’t know. Someone ask a question. There are at least six of you here.

I don’t know how many more are going to come in. There are some of you. Someone has to have a SQL Server question. You can’t just come here to hear me complain about things or blab on and on about blog posts and whatnot.

Someone has to have a question about SQL Server. Please, God, someone have a question. Come on, there’s, like, 10 of you. Laura says, do you have any thoughts on replacing temp tables with in-memory OLTP?

Yeah, don’t. Hecaton is like herpes. It doesn’t have the nerve to kill you.

It just hangs around being awful for the rest of your life and flaring up at inappropriate times. So, I just have not found anything compelling about Hecaton. It seems like every time…

And this is not just me. This is some very smart people that I’m friends with. Every time they think that they have a specific problem with latches that Hecaton might solve, it is an absolute dead end.

Absolute dead end. In SQL Server 2019, TempDB is going to use… Well, you can use TempDB as, like, an Hecaton-y thing anyway.

I would pry this whole lot to see if that happens. But, whereas, I guess the question is, why? Why?

What are you trying to fix? What is a problem that we’re trying to solve here? What do you think Hecaton is going to make better in your stored procedure that you want to use in memory for?

That’s the big question. What’s going to get better about it? What’s going to get better? I don’t know.

Do, do. Do, do. Do, do. Try to have better indexing of… What on earth? You can index TempTables now.

What’s missing from your current TempTable indexing that Hecaton is going to provide a safe and secure solution for? That’s what I’m curious about.

Too slow. Well, so here’s the thing. What’s too slow? Creating the index or populating the Temp…

or populating the table? Josh says Hecaton indexes are extra confusing. Yeah, they are. So who knows how many hash buckets you should set up? Creating the index.

So… Creating the index is too slow. Okay. Fair enough. See, this is one of those things where it’s like, we’re going to end up going down a real rabbit hole.

Because I’m going to ask you what kind of index. And what kind of data types you’re indexing. And how many rows it is. And a lot of other stuff.

And this is… I hope you’re prepared. Because this can go on for a long, long time. I know.

Then you’d have to actually look at code. Bad news is you’d actually have to look at code to implement Hecaton. and get it set up and running there. If you don’t…

If creating an index is slow now on a Temp table, I don’t think it’s going to be any faster in Hecaton. It’s not free.

Nothing is free. Nothing is free. Yeah, no. Well, you know, my…

So Hecaton is very specifically designed to deal with locking and lashing issues. The in-memory portion, I think, is…

The misgiving around it is that it’ll make any workload faster. And that’s not really true. And the use cases where I’ve seen Hecaton be successful is with really large-scale, fast data ingestion into tables where data is not going to live for very long.

So the example that I always give, because it’s the example that I’ve seen work best, was with online gambling, where data had to come in very fast. And we cared about that data for a very short amount of time, where we wanted very fast updates and being able to get to that data to be snappy and not get blocked up and locked.

And then after like an hour or so, or whenever the betting thing is over, they get pushed out to regular on this table. So that’s the only time.

It’s the only time I’ve ever seen Hecaton be successful. For every other weird niche thing that someone’s like, oh, I bet if we just did this in memory, it’d be faster.

It has never worked out. Never worked out. I don’t know. It’s like, people hear about these features and they, I don’t know, the pamphlets that Microsoft comes up with for these things are amazing because they make it seem like they’re going to fix every single problem that you’re having.

They just sound like this, like golden acres retirement home for your data. And geez, never, never seems to go.

Yeah. So like with the Lars, I guess to put it, put it as succinctly as possible. you can try it, but be very careful with the database that you try it in because once you have created an in-memory file group in a database, one cannot drop an in-memory file group.

One must drop and recreate the database. So if you’re going to try it somewhere, create a new database and enable things there. Don’t do it with a database you care about.

So that’s about all I got. Let’s see. TZH X says, do you have a rule of thumb for how wide a table is too wide? Currently have a bunch of core tables in the system with a hundred plus columns each.

Yeah. You’re about there. So I think one way to, one way to phrase this really well is the maximum number of columns you can have in a table is a thousand and 24.

And a really smart friend of mine, Canadian fella, says it has, has sort of a, what do you call is it? Michael’s Michael J. Swartz 10% rule with, if you’re using 10% of a maximum of a limit, right?

Cause these are limits. They are not goals. They’re not things that you are trying to attain. They are things that they are, they are like, like the, the capacity at which SQL Server will stop functioning. So you’re, you’re right about there.

And the thing that really sucks about a hundred column tables is they’re really, really difficult to index. Well, unless you don’t care about read speeds from them, unless you’re just dumping data in there and not really doing anything with it, then it becomes really hard to index that.

Cause people are, people are going to you know, they’re going to want to search on, on weird sets of columns. And they’re going to want to select weird sets of columns and potentially order by weird sets of columns.

And all that adds up to is a lot of a heartache and pain and trying to index those things. So when I start seeing tables like that, that there are three patterns that I look out for very specifically.

one is column names that have prefixes. So like customer name, customer number, customer address, customer, things like that. Cause those should all be in a table called customer. The other thing that I look out for is, uh, columns that end in numbers.

So like phone one, phone two, phone three, email one, email two, email three. I’m also, I also pay attention to those. Cause those should probably be in their own sort of like entity, attribute value type table, like a, like a long narrow table where, you know, you link up, you know, like, you know, different, different, uh, entities to different attributes and different values than EAV table.

It’s amazing how that works out. The third thing that I look out for when I see very wide tables is, uh, clusters of missing index requests.

So like, like a lot of the times when I’ve seen crazy tables like that, um, what’s, what’s jumped out to me is that, um, missing index requests will be very specific around, which columns are in them.

So the, so like you’ll have a very similar set of search columns and a very similar set of select columns. And oftentimes you can find ways to break, to normalize that table, to break that table apart into separate tables based on what people are searching on within that table.

So you might find like, you know, setting, let’s say, like say you find 12 missing indexes and out of those 12, like four of them are selecting columns one, two, and three.

And the where it causes on columns four or five and six. And then, and the, another one, there’s like four requests and now there’s some, you know, sort of tanglement between columns eight, nine and 10 and 11, 12 and 13.

So like, you can usually find patterns within the missing index requests and within the column names, they can give you some pretty good direction and breaking. I keep saying breaking. I mean, normalizing.

It don’t mean you’re breaking the table. You’re not hurting the table. You’re not breaking your SQL Server. You’re normalizing your data, which is a wonderful thing to do. If you want more advice, if you want a lot of really good advice on doing that, Lewis Davidson has a book called relational SQL Server database design or something like that.

Let me go, let me go over to Amazon and grab a link for it. Cause it’s, I’m probably, I’m probably getting the title or something wrong.

I wish Davidson equal server design. It’s a book that I have in my bookshelf too. It’s not like I just tell people to buy these things and then screw off and let you do all the hard work.

It’s a book that I actually own and I’ve actually read. So let me stick that link in there. There you go. Wonderful.

We have a link. That’s beautiful. That’s a good thing. And let me actually, while I’m doing that, Michael, I should spell Michael, J sport 10% rule. There we go.

Swartz 10% rule. So I would send this stuff over to your developer. I would, I would get a copy of that book for your developers. And I would, I mean, unless you’re the developer, in which case I’m sorry for making money, but you know, could sell, send them, send them, show them that book, show them that link.

Say maybe, Hey, pal, we need to fix this. We need to do better. We need to do better and be better at our SQL Server, relational database design and implementation.

It’s, it’s crazy. So like, I’m like around the five year mark of consulting. I mean, obviously not independent, like, like just sort of generally like general consulting.

And I’m going to say something, uh, that is, is very, okay. I think it’s going to be annoying to a lot of people.

And as your problems are not special, your problems are very fundamental. the problems that your database is having is because you did something weird and wrong. You embraced wide tables.

You did not embrace clustered indexes. You embrace scale, our functions. You, uh, you know, did not pick a decent indexing strategy. There are so many just basic, like you, you chose data types really poorly.

Everything is a, is a long string for some reason. Everything is, uh, mistyped across tables. This, it’s very often, just very fundamental, easy fix things that like you, like should have been done from the get go, but just weren’t.

And, and it screws everything up. And it, it breaks my heart to come in and see like the same problems over and over again, where it’s just like you, like if someone had just made a couple better decisions at the outset, you could have avoided years of problems.

Years of problems. Let’s see. Kapil asks, can a query have a long running query on terabytes of database? Zero granted query memory.

I see some on large queries and don’t get why. Yes. Uh, so query memory grants. So like every query gets some memory. Because every operator.

Requires some memory to like figure out what state it’s in and what it’s doing and what it’s up to. So that every query gets some memory memory grants are very specific to a couple operations. One of them is sorts, right?

So if you sort data, you’re going to require a memory grant to do that. Uh, if you hash data, whether it’s a hash match aggregate or a hash join, you’re going to require memory to do that. And if your query requires, or if your query goes parallel, you’ll require a little extra memory to manage the exchange, the, the parallel exchanges, the buffers and the parallel exchanges.

So there are, there are three things that base, uh, in a nutshell require memory. There are some less, uh, frequent things like optimized nested loops join, which also will ask for a memory grant. And there’s, using like doing like inserts, the columnstore will also ask for a memory grant, but, uh, yeah, it’s, it’s entirely possible to have a query run across terabytes of data and not have to sort or hash anything.

It’s entirely possible for that to happen. And for a query, not to ask for a memory grant to do any of that work.

So fun stuff, huh? Like, like it’s, it might not be a great query. It might be a very slow, terrible query because at that point, I’m picturing a query with like, like lots of like little nested loops joins and, or maybe like in like index supported merge joins where, uh, memory just isn’t required to do any of that stuff.

So yeah, it’s totally possible. Uh, I find it a little suspicious that, that it’s happening on terabytes of data. Cause usually when you get terabytes of data involved, SQL Server is, uh, is pretty keen on doing some hash joins there, but who knows, who knows if you have a query plan that you can share and that you have a question about, uh, that would be a good question for DBA dot stack exchange.

Dot com. That’d be a wonderful place to ask a longer version of that, where perhaps you could provide some more detail and people could give you much more detailed answers. But I think in a nutshell, that should, that should get you going in the right direction.

Let’s see here. TZH asks, I’ve got a handful of nonclustered indexes on the clustered index key with separate columns included to cover some repeat queries. It just seemed dirty.

Yeah. I mean, you’re between a rock and a hard place, right? It’s either you, you, you got these wide tables. And if you don’t index them, uh, people are going to complain and it’s hard to index them. And if you over index them, people are going to complain.

And, uh, it all sucks. It all sucks. Uh, James says, how do I sign up to get the blogs you write sent to my email?

I added both my personal and work email to the newsletter, but never seemed to get any emails. I have checked junk mail and filters. Um, um, I don’t know what would, what would be going wrong there.

I, uh, that email list seems to work because, um, uh, what do you call it? I get, like, I see the email comes to me and I get bounce backs from everyone.

Who’s like, I get everyone’s out of office reply. Cause I’m an idiot and I don’t know how to change that in MailChimp. And when I think about Googling it, I find something better to do.

So I, but it’s, it’s kind of nice to see it’s working. It’s like, it’s like my way of knowing that I’m not just like alone in my office for eternity. Uh, but yeah, uh, I’m not sure if you shoot, if you send me an email, like if you use the contact form on my site to send me an email, um, I can, I can, I can look at your email address and look in MailChimp and kind of see what’s happening there.

It’s entirely possible that like, for some reason, I don’t know, maybe you spelled your email address wrong or, uh, or, or maybe you’re like, whatever your, your email host is just, just hates stuff from MailChimp so badly that it doesn’t even make it to spam, but just gets like immediately quarantined and junked.

It’s also entirely possible that I’ve been blacklisted again, because I don’t know. It’s happened before. I got an SSL certificate and I thought things were cool and people are still like, yeah, we don’t trust you.

I’m like, I got a certificate. I paid GoDaddy like 125 bucks for this damn certificate. How can I be untrustworthy? How, how is, how tell me how that works? Very annoying.

I don’t know. Anyway, Forrest, my furry friend. Oh, by the way, I meant to say if, if you sent me your address last week to get stickers mailed out to you, stickers were put in the mail, uh, this week.

So you should be getting them eventually. I don’t know when. Be honest with you. I wish I could, I wish I could predict and control the mail. Unfortunately, that is where my powers run out.

Anyway, Forrest says, or asks, do you ever find that knowing about memory management internals is helpful? It’s helpful when I create very specific problems that deal with memory management.

I would say that for the general public who have a pretty well-defined set of SQL Server issues, memory management is almost never, um, the issue.

The issue is that SQL Server is managing like 12 gigs of memory when it needs like 128 gigs of memory. Uh, so, you know, it’s, it’s good stuff to know.

It’s good. Like, like SQL jeopardy stuff to know. There’s a lot of good SQL jeopardy stuff to know, but you got to keep stuff, that stuff like back here, the stuff you got to keep up here is way different. Um, so yeah, it’s, it’s good to know.

It’s nice to know about, but, uh, whether it’s ever, whether I’ve ever like walked into a customer site and been like, uh, ha, I see you’ve got stolen pages. Let’s solve that.

Let’s crack that caper. And it’s been like some weird problem. It’s never been a weird problem. It’s always been like, well, you, you, Oh, you put like 500 gigs of, of memory inside SQL Server, but you forgot to raise max server memory from 64 gigs.

When you made the change or like, you’ve got a SQL Server, you are like, you’ve got a VM host with like eight SQL servers on it. And they’re all just fighting over memory constantly.

Or like, just like, like, like stuff has never been like, like, Oh, SQL services bad managing memory. It’s always been like, Oh no, there’s a people problem.

We have a big people problem here. So there’s that. Kapil says, perfect. I got it. Yes, indeed. It’s one heck of a crappy query. Too many inner joins with no sorting in a serial plan.

Woo. We, so wow. Serial plan. So we have a serial plan with no sorting over terabytes. Is there a scalar valued function involved here or a table variable involved here? Because there, there is something amok when we have a query that looks like that over that much data.

And, and there’s like no memory grant or sorting or anything that something stinks about that. Something is stinky in there.

Something is very, very stinky in there. Kapil falls. Are you planning to release some training videos? I learned a lot from, you’re available at Brent’s query tuning classes.

Yes, I am. So I’m in, I’m in a weird, weird position. And I wrote about my weird position a little bit in, in my, in my six months of consulting on my own blog posts, which will be out.

I don’t know, but I think on the anniversary of, on the anniversary of me getting laid off. So it’ll be like June 3rd, which is exactly six months from January 3rd, which is when I got beheaded.

Or when I, when I, when I had a fall from grace and became unfamous or whatever, whatever you want to call it. But yeah, so it’s, it’s been a tough mix of, you know, trying to write good training material.

In other words, like from scratch. So it’s like, I, I do like, well, I’m not, I will never ever be accused of being accused of being a perfectionist. I, I do like to provide high quality or as high quality as I’m capable of material with, with the training.

So I do try to like, you know, have, have nice pictures and have things be laid out clearly and look nice. And, and the other thing is that I don’t want to just repeat myself. So I wouldn’t want to just like completely rehash training videos that I had recorded that are up on Brent’s site.

If I’m going to, if I’m going to do this thing, it’s got to be me. So it’s, it’s tough because now I have to sort of find new ways to say things or new ways to present things.

Uh, so it is, it is writing all the material from scratch and, and it’s, it’s been going a bit more slowly than, than I’d like just because, you know, I mean, it’s a great problem to have because I, I have enough like consulting work coming in where it takes serious chunk, serious bites out of my, my time during the week where I can’t sit and dedicate it to, to writing the training, but it’s getting written.

Um, you know, I’m going to, I’m going to take, uh, some of the material from the, the, the server tuning pre-con that I’ve been doing. And, uh, I’m going to, that’s going to be like worked into some of the more advanced material.

Uh, a lot of the more advanced, uh, query tuning stuff is written right now. My, uh, my outline is, uh, like, like beginner stuff, uh, which I’m going to call starting SQL.

And that’s going to cover, um, that’s going to like do like, uh, sort of like a, like a jump, jump right into like, this is what a query does.

This is how, why it’s fast, why it’s slow. This is indexes, uh, you know, then going deeper into what is an index, what’s it, what’s a weight, you know, what’s a query plan. So like get, like getting like from like, like, like beginner stuff, but like, you know, not like, I think you’re a dummy beginner stuff.

Like I’m going to like teach you the really important, stuff about those things. Uh, then after that, I’ll probably do like a little bit of internals, not like, not like a book of boring, like, this is a database page.

This is a slaughter a type internals, like, like the stuff that I’ve found useful over the years. Um, then we’ll do like hardware stuff and then indexes more like more advanced index stuff. And then more advanced execution plan stuff.

And then query tuning. Um, I’m also hoping to have a very special guest, maybe do some, some stuff on columnstore. But I can’t say who or when, but that would be nice if that happened too.

So yeah, that’s my, that’s my plan. And, uh, and, and Josh to, to answer your email.

No, I don’t, I don’t have your address anymore. That was in my old email account. I don’t have access to any longer. Uh, so if you, if you, if you want to send me your, your actual address, that would be actually, I didn’t, I didn’t read enough of your email.

What the hell? What is this? I don’t even know what this is. Tastings. I’m not, I’m not going to you. You’re annoying me.

Email is terrible. email is the worst thing in the world. Darren says, he’s always found my classes, very informative and entertaining. Thank you, Darren.

I appreciate it. Uh, I’m glad someone does because most of the time, uh, when, whenever I’m talking to the camera, I’ll leave my office and my wife is staring at me like there’s something terribly wrong with me. I’m glad someone out there is, is, is entertained and informed.

The things that I say in here, otherwise, otherwise I don’t, I don’t know what I do. I don’t know. If, if you, if you, if you were one or the other, I would be, I would be ecstatic.

If you were informed or entertained by me, I would be thrilled. But the fact that you’re both, wow. I don’t, I don’t even know what to say to that. Enjoy, enjoy the stickers that I say to that.

Uh, yeah, that’s the thing. I don’t, I don’t carry over well to a lot of crowds. I have, I have a very specific set of people. I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I’ll tell you that that’s probably the hardest part about consulting is, is not like the consulting poker face or like, like the business stuff or the, like the, like the landing clients part.

It’s, it’s like the, the, how can I say this to like, how can I say this to people who I’ve never met in a way where like, I’m not gonna, I’m not gonna make everyone too angry.

And I think like, like that’s probably the hardest thing is, you know, like I, I need to say things in a way that they keep me interested in it. Entertain me.

But then at the same time, like I, I know someone’s not going to be happy with it. Like someone, someone’s going to be unhappy with it. Someone is going to be mad at me or, or, or think that I’m inappropriate or something.

So, ah, whatever. It’s a good time. You know what? Uh, if, if, if I had to pick a different way to make a living, I’m not sure what it would be.

Actually, I can’t, you know what I would do? I would open up a laundromat with a bar in it and I would only have it open from like, I don’t know, like 10 AM to like 8 PM, maybe 9 PM.

Cause I don’t want to like, I don’t want to have like an all night laundromat bar thing. I like, like, like, like, that’s like, like, like just washing machines full of pee would vomit would be the thing there. So I would open up a laundromat with a bar and I have very limited hours and I would just sit there and watch people do laundry.

That’s what I would do. Maybe I’d make a friendly conversation. I’d be the bartender, I guess.

I have a very limited drink menu. Cause I’m not very good at mixing things. Beer. The world doesn’t have enough shot in beer bars.

Everything’s a, everything’s a mixology bar. Everything’s going to have muddled, whatever in it. Shaved ice and rinds of things shot in a beer. Never did anyone wrong.

Anyway, uh, we’re at a, we’re at about a half hour here. And, um, uh, uh, uh, oh boy, this is a question. Hang on.

I just went through one of Joe’s blogs regarding soft Numa starting CWL server 2016. Per your experience, have you turned that feature off due to performance issues as raised by Joe in that blog? You should leave a comment on, on Joe’s blog post and ask him about it.

Cause Joe is by far a much, a much bigger expert on that Numa stuff than I am. So leave a comment with your question and, uh, mate, mate, and Joe will get back to you because he’s contractually obligated to is my guest.

No, I’m kidding. He’s not, he might ignore you, but if he does, I’m sorry. I’m sorry. I can’t control Joe. I was at first.

I was like, you know, if I’m going to do this, I should like, you know, say, okay, I’ll schedule your post to go out. And then I really just give Joe, let Joe off his leash, let Joe off his chain and do his Joe thing. Be ridiculous.

Trying to contain that man. Anyway, uh, that’s about a half hour. Uh, I’m going to, uh, I’m going to get going and, uh, get back to work or whatever you want to call it. Uh, thanks for joining me.

Uh, and I will see you most likely next week, unless something terrible happens. I’m kidding. Nothing. Unless I win the lottery, which would be wonderful. But if I do, if I win the lottery, I’m going to do this, uh, really drunk and tell you all what I actually think about you.

Goodbye. Have a great, great long weekend. if you are memorializing anything. Goodbye. Bye.

Bye. Bye. Bye. Thank you.

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.

The Waiting Life Of A SQL Server Query

Smoking Ain’t Allowed In School


While working on my new training, part of what I want to show people is how to match what happens in a query plan to the type of wait stats they’ll see on their servers.

Take this query plan for example:

SQL Server Query Plan
Nose Boop™

There are THINGS GOING ON HERE! Scans, parallelism, sorting, spilling.

What do the waits look like for this query?

SQL Server Wait Stats

Spy vs Spy


It’s fairly easy to focus in on what a session is doing using sys.dm_exec_session_wait_stats, though there are some caveats about when the numbers reset.

On my laptop, I can just open a new SSMS tab without worrying about connection pooling or any weird app stuff.

When I do, these are the waits I see while the query runs.

Every single one of those waits is accounted for by something that happened in the query plan (or its attributes).

Breakdown


Our pages weren’t in memory, so we read them from disk (PAGEIOLATCH_SH), we had to allocate buffer pool memory (MEMORY_ALLOCATION_EXT), we used parallelism (both CX waits), we needed memory to sort data (RESERVED_MEMORY_ALLOCATION_EXT), we didn’t get enough so we spilled (IO_COMPLETION), and we returned data to SSMS (ASYNC_NETWORK_IO).

LATCH_EX can be a lot of things. In a parallel plan, it can be associated with exchange buffers and the parallel page supplier.

The goal here is for you to both look at a server’s wait stats and come up with a mental image of the types of queries you’re going to see, and be able to tie query plan operators and attributes back to wait stats.

Thanks for reading!

SQL Server Joins With OR Clauses = ?

A Long Time Ago


I had to write some hand-off training about query tuning when I was starting a new job.

As part of the training, I had to explain why writing “complicated logic” could lead to poor plan choices.

So I did what anyone would do: I found a picture of a pirate, named him Captain Or, and told the story of how he got Oared to death for giving confusing ORders.

This is something that I unfortunately still see people doing quite a bit, and then throwing their hands up as queries run forever.

I’m going to show you a simple example of when this can go wrong, and also beg and plead for the optimizer team to do something about it.

Big Bully


“Write the query in the simplest way possible”, they said.

So we did, and we got this.

SELECT u.Id, MAX(p.Score)
FROM   dbo.Users AS u
JOIN   dbo.Posts AS p
    ON u.Id = p.OwnerUserId
    OR u.Id = p.LastEditorUserId
WHERE  p.PostTypeId IN (1, 2)
GROUP BY u.Id;

Note the OR in the join condition — we can match on either of those columns.

Here’s the index we created to make this SUPERFAST.

CREATE NONCLUSTERED INDEX 36chambers
    ON dbo.Posts ( OwnerUserId, LastEditorUserId, PostTypeId )
    INCLUDE ( Score );

If we’re good DBAs, still doing as we’re told, we’ll read the query plan from right to left.

The first section we’re greeted with is this:

SQL Server Query Plan
No Cardio

We spend a full minute organizing and ordering data. If you want to poke around, the plan XML is here.

The columns in the Compute Scalars are OwnerUserId and LastEditorUserId.

Next in the plan is this fresh hell:

SQL Server Query Plan
Squozed

24 seconds seeking into the Users table and joining that to the results of the Constant Scans, etc.

What’s a little confusing here is that the scan on the Posts table occurs on the outer side of Nested Loops.

It’s also responsible for feeding rows through the Constant Scans. That’s their data source.

Overall, this query takes 1:36 seconds to run.

My gripe with it is that it’s possible to rewrite this query in an obvious way to fix the problem.

Magick


Using a second join to Posts clears things up quite a bit.

SELECT u.Id, MAX(p.Score)
FROM   dbo.Users AS u
JOIN   dbo.Posts AS p
    ON u.Id = p.OwnerUserId
	AND p.PostTypeId IN (1, 2)
JOIN   dbo.Posts AS p2
    ON u.Id = p2.LastEditorUserId
	AND p2.PostTypeId IN (1, 2)  
GROUP BY u.Id;

I know, it probably sounds counterintuitive to touch a table twice.

Someone will scream that we’re doing more reads.

Someone else will faint at all the extra code we wrote.

But when we run this query, it finishes in 10 seconds.

SQL Server Query Plan
That’s better!

This plan does something a bit different. It joins the nonclustered index we have on Posts to itself.

SQL Server Query Plan
Seminar

The optimizer has a rule that makes this possible, called Index Intersection.

Extra Magick


A more accurate description of what I’d want the optimizer to consider here would be the plan we get when we rewrite the query like this.

SELECT u.Id, MAX(p.Score)
FROM   dbo.Users AS u
CROSS APPLY
    (
        SELECT p.Score
        FROM   dbo.Posts AS p
        WHERE u.Id = p.OwnerUserId
        AND p.PostTypeId IN (1, 2)
        
        UNION ALL 
        
        SELECT p2.Score
        FROM  dbo.Posts AS p2
        WHERE u.Id = p2.LastEditorUserId
        AND p2.PostTypeId IN (1, 2)  
    ) AS p
GROUP BY u.Id;
SQL Server Query Plan
Mush.

This query runs a bit faster than the second one (around 7 seconds), and the plan is a little different.

Rather than a Hash Join between the index on the Posts table, we have a Concatenation operator.

The rest of the plan looks like this:

SQL Server Query Plan
Yabadabadoo!

The optimizer has a rule that can produce this plan, too, called Index Union.

Problemagick


The thing is, these rules seem to be favored more with WHERE clauses than with JOINs.

CREATE INDEX ix_fangoria
    ON dbo.Posts(ClosedDate);

SELECT COUNT_BIG(*) AS records
FROM dbo.Posts AS p
WHERE p.ClosedDate IS NULL
OR p.ClosedDate >= '20170101'
AND 1 = (SELECT 1);
SQL Server Query Plan
Index Union
CREATE INDEX ix_somethingsomething 
    ON dbo.Posts(PostTypeId);

CREATE INDEX ix_wangchung 
    ON dbo.Posts(AcceptedAnswerId);

SELECT COUNT_BIG(*) AS records
FROM dbo.Posts AS p
WHERE p.PostTypeId = 1
AND p.AcceptedAnswerId = 0
AND 1 = (SELECT 1);
SQL Server Query Plan
Index Intersection

Knackered


It is possible to get these kind of plans with joins, but not without join hints and a couple indexes.

CREATE INDEX aussie 
    ON dbo.Posts (OwnerUserId, PostTypeId, Score);

CREATE INDEX kiwi 
    ON dbo.Posts (LastEditorUserId, PostTypeId, Score);
SELECT u.Id, MAX(p.Score)
FROM   dbo.Users AS u
JOIN   dbo.Posts AS p
    WITH (FORCESEEK)
    ON u.Id = p.OwnerUserId
    OR u.Id = p.LastEditorUserId
WHERE  p.PostTypeId IN (1, 2)
GROUP BY u.Id;

There’s more background from, of course, Paul White, here and here.

Even with Paul White ~*~Magick~*~, the hinted query runs for ~16 seconds.

If you remember, the Index Intersection plan ran for around 10 seconds, and the Index Union plan ran for around 7 seconds.

SQL Server Query Plan
2slow

This plan uses Index Union:

SQL Server Query Plan
Spillzo

Thanks for reading!

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.

SQL Server T-SQL Protip: watch those TOPs without ORDER BY

In the documentation for TOP, the following is listed as a best practice:

In a SELECT statement, always use an ORDER BY clause with the TOP clause. Because, it’s the only way to predictably indicate which rows are affected by TOP.

Let’s work through a real world example.

The good


One of the great things about the “Top Resource Consuming Queries” query store SSMS report is that it is always able to render the query plan, even for very complex queries. I’m not aware of a pure T-SQL solution that can avoid requiring the end user to save xml to files in all cases. The report nearly always takes a long time to run, so it’s easy to capture the T-SQL that powers the grid details version:

DECLARE @results_row_count INT = 100,
@interval_start_time DATETIMEOFFSET = '2019-05-24 15:30:00 +00:00',
@interval_end_time DATETIMEOFFSET = '2019-05-24 18:00:00 +00:00';
 
SELECT TOP (@results_row_count)
    p.query_id query_id,
    q.object_id object_id,
    ISNULL(OBJECT_NAME(q.object_id),'') object_name,
    qt.query_sql_text query_sql_text,
    ROUND(CONVERT(float, SUM(rs.avg_duration*rs.count_executions))*0.001,2) total_duration,
    SUM(rs.count_executions) count_executions,
    COUNT(distinct p.plan_id) num_plans
FROM sys.query_store_runtime_stats rs
    JOIN sys.query_store_plan p ON p.plan_id = rs.plan_id
    JOIN sys.query_store_query q ON q.query_id = p.query_id
    JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
WHERE NOT (rs.first_execution_time > @interval_end_time OR rs.last_execution_time < @interval_start_time)
GROUP BY p.query_id, qt.query_sql_text, q.object_id
HAVING COUNT(distinct p.plan_id) >= 1
ORDER BY total_duration DESC;

Note the presence of the ORDER BY. I get exactly the results that I was expecting:

a41 good

The bad


If I ask for extra details (who doesn’t want more details?), a significantly more complex query is generated:

-- grid format query with additional details
-- grid format query with additional details
DECLARE @results_row_count INT = 100,
@interval_start_time DATETIMEOFFSET = '2019-05-24 15:30:00 +00:00',
@interval_end_time DATETIMEOFFSET = '2019-05-24 18:00:00 +00:00';
 
With wait_stats AS
(
SELECT
    ws.plan_id plan_id,
    ws.execution_type,
    ROUND(CONVERT(float, SUM(ws.total_query_wait_time_ms)/SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms))*1,2) avg_query_wait_time,
    ROUND(CONVERT(float, SQRT( SUM(ws.stdev_query_wait_time_ms*ws.stdev_query_wait_time_ms*(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms))/SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms)))*1,2) stdev_query_wait_time,
    CAST(ROUND(SUM(ws.total_query_wait_time_ms/ws.avg_query_wait_time_ms),0) AS BIGINT) count_executions,
    MAX(itvl.end_time) last_execution_time,
    MIN(itvl.start_time) first_execution_time
FROM sys.query_store_wait_stats ws
    JOIN sys.query_store_runtime_stats_interval itvl ON itvl.runtime_stats_interval_id = ws.runtime_stats_interval_id
WHERE NOT (itvl.start_time > @interval_end_time OR itvl.end_time < @interval_start_time)
GROUP BY ws.plan_id, ws.runtime_stats_interval_id, ws.execution_type ),
top_wait_stats AS
(
SELECT TOP (@results_row_count)
    p.query_id query_id,
    q.object_id object_id,
    ISNULL(OBJECT_NAME(q.object_id),'') object_name,
    qt.query_sql_text query_sql_text,
    ROUND(CONVERT(float, SUM(ws.avg_query_wait_time*ws.count_executions))*1,2) total_query_wait_time,
    SUM(ws.count_executions) count_executions,
    COUNT(distinct p.plan_id) num_plans
FROM wait_stats ws
    JOIN sys.query_store_plan p ON p.plan_id = ws.plan_id
    JOIN sys.query_store_query q ON q.query_id = p.query_id
    JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
WHERE NOT (ws.first_execution_time > @interval_end_time OR ws.last_execution_time < @interval_start_time)
GROUP BY p.query_id, qt.query_sql_text, q.object_id
),
top_other_stats AS
(
SELECT TOP (@results_row_count)
    p.query_id query_id,
    q.object_id object_id,
    ISNULL(OBJECT_NAME(q.object_id),'') object_name,
    qt.query_sql_text query_sql_text,
    ROUND(CONVERT(float, SUM(rs.avg_duration*rs.count_executions))*0.001,2) total_duration,
    ROUND(CONVERT(float, SUM(rs.avg_cpu_time*rs.count_executions))*0.001,2) total_cpu_time,
    ROUND(CONVERT(float, SUM(rs.avg_logical_io_reads*rs.count_executions))*8,2) total_logical_io_reads,
    ROUND(CONVERT(float, SUM(rs.avg_logical_io_writes*rs.count_executions))*8,2) total_logical_io_writes,
    ROUND(CONVERT(float, SUM(rs.avg_physical_io_reads*rs.count_executions))*8,2) total_physical_io_reads,
    ROUND(CONVERT(float, SUM(rs.avg_clr_time*rs.count_executions))*0.001,2) total_clr_time,
    ROUND(CONVERT(float, SUM(rs.avg_dop*rs.count_executions))*1,0) total_dop,
    ROUND(CONVERT(float, SUM(rs.avg_query_max_used_memory*rs.count_executions))*8,2) total_query_max_used_memory,
    ROUND(CONVERT(float, SUM(rs.avg_rowcount*rs.count_executions))*1,0) total_rowcount,
    ROUND(CONVERT(float, SUM(rs.avg_log_bytes_used*rs.count_executions))*0.0009765625,2) total_log_bytes_used,
    ROUND(CONVERT(float, SUM(rs.avg_tempdb_space_used*rs.count_executions))*8,2) total_tempdb_space_used,
    SUM(rs.count_executions) count_executions,
    COUNT(distinct p.plan_id) num_plans
FROM sys.query_store_runtime_stats rs
    JOIN sys.query_store_plan p ON p.plan_id = rs.plan_id
    JOIN sys.query_store_query q ON q.query_id = p.query_id
    JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
WHERE NOT (rs.first_execution_time > @interval_end_time OR rs.last_execution_time < @interval_start_time)
GROUP BY p.query_id, qt.query_sql_text, q.object_id
)
SELECT TOP (@results_row_count)
    A.query_id query_id,
    A.object_id object_id,
    A.object_name object_name,
    A.query_sql_text query_sql_text,
    A.total_duration total_duration,
    A.total_cpu_time total_cpu_time,
    A.total_logical_io_reads total_logical_io_reads,
    A.total_logical_io_writes total_logical_io_writes,
    A.total_physical_io_reads total_physical_io_reads,
    A.total_clr_time total_clr_time,
    A.total_dop total_dop,
    A.total_query_max_used_memory total_query_max_used_memory,
    A.total_rowcount total_rowcount,
    A.total_log_bytes_used total_log_bytes_used,
    A.total_tempdb_space_used total_tempdb_space_used,
    ISNULL(B.total_query_wait_time,0) total_query_wait_time,
    A.count_executions count_executions,
    A.num_plans num_plans
FROM top_other_stats A LEFT JOIN top_wait_stats B on A.query_id = B.query_id and A.query_sql_text = B.query_sql_text and A.object_id = B.object_id
WHERE A.num_plans >= 1
ORDER BY total_duration DESC
)

Now we have not 1, not 2, but THREE TOP operators! But only one of them has an ORDER BY. The results are completely different, and are pretty much useless:

a41 bad

The ugly


This has nothing to do with TOP as far as I know, but I included it just for fun:

a41 ugly

Final thoughts


All of you developers out there should watch your TOPs and make sure you’re using ORDER BY as needed. Otherwise, you might end up with annoyed end users writing blog posts about your code.

Thanks for reading!

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.

Independent Consulting: Six Months And Running

Knock Knock


There’s nothing technical in this post.

It’s just sort of a general accounting of what six months of independent consulting has looked like for me.

Some things I’ve learned, things I’m happy with, things I’m unhappy with. I dunno.

We’ll see where this goes as I’m writing it. Don’t count on it being terribly insightful.

There are countless people who are owed great amounts of gratitude for the advice, encouragement, referrals, and all the other Spackle needed to get things off the ground.

Words in a blog post are insufficient. I’m plotting my thank yous carefully.

Caliente


I’ve had 23 clients in 6 months, not counting work that Mike Walsh has outsourced to me (insert largest possible thank you here).

The projects have varied quite a bit in length and price, so don’t go trying to reverse engineer my income.

I’m not as comfortable as some other people in talking about that, though I’m entirely willing to tell you I made $300 in January.

The only thing I’ll say is that I made just about my base salary last year at the beginning of May.

I’m very happy with that, but the thing I want to share is this: lump slums are really misleading.

I learned this from Brent.

An equally large thank you should be here, too, for probably the best education one could ask for in how to run a consulting business.

Bill Factor 100


Whenever money comes in, I pull 30% out to pay taxes with.

I stick it in a savings account, so maybe it’ll earn whatever pittance interest rates are these days.

Just to use a round number: If I charge someone $1000, $300 leaves immediately.

When you’re setting prices on your time, factoring in that 30% is really important.

The other things you need to consider are, obviously, your expenses.

I have rent, I have to fully pay for my own insurance now, and I have the ever-present work-harder motivation that is a wife and two kids.

Plus the usual phone, internet, utilities, and other recurring expenses.

To put $1000 in perspective: if I charge someone $1000, I still owe my insurance company $800.

To put it further in perspective, I’d still owe my landlord $2000.

And I owe Uncle Sam™ that $300. Whew.

If you ever wonder why consultants seem expensive, it’s because we have to factor a lot of stuff in.

Every price tag has that stuff (plus the constant fear of a slow month) built in to it.

Happy Happy


So what am I happy with? I have a thing that’s mine. I can do whatever I want with it.

I’ve found it fun building my own “brand”, even if I’m not terribly good at building a brand.

I’m happy with the work I’ve been doing, too. With Brent, it was very much hands off analysis.

I’ve been getting back into actually tuning stuff.

And you know what? It’s rewarding to know that the advice you give people actually works.

It’s not all fun and games, but what is?

At least I choose what I say yes or no to, so I only have myself to blame.

I’ve gotten to travel a bit more, though the locales haven’t exactly been exotic.

Downers


Stuff I’m unhappy with is largely cosmetic.

I hate the way my website looks. It’s dark, and it doesn’t sell anything very well.

All of the choices I made at the very beginning were very much hair-on-fire, oh-crap-I-gotta-make-this-work-quick.

Believe it or not, layoffs don’t come with a lot of advance notice.

Anyway, I feel like it shows when I look at my website. And I just don’t have time to do anything about it right now.

I’m also not great at marketing.

I mean, I’m pretty good at saying funny things about what I’m doing, but I have no idea how to get it in front of more faces, or get more people to say “yes”.

That psychology escapes me.

I’ve read books. I’ve seen doctors. I’ve talked to people. I just don’t think I have that gene.

Time Management


The one thing I’m really beating myself up about is training.

Writing it from scratch is hard.

And long.

Long and hard.

Like a morning without drinking.

It’s coming along, but all the “keep the lights on” work seems to eat away at the time I want to spend building it.

I’M A BIT OF A PERFECTIONIST (ha ha ha)

No but really. I have a lot to write down.

I’m plotting stuff that can take you from beginner to wherever I’m at currently. Hopefully that line keeps moving.

Futuristic


Alright, yeah. This thing.

My hope is that I can focus on getting training written and produced, even if it’s slower than I’d like.

I realized it would be daft to work on a set of my own data analysis scripts, so I’ll be working on the First Responder Kit as time allows.

When I’m comfortable with the amount of money in the bank, I’ll probably get the website a facelift.

Maybe I’ll magically get good at marketing.

Thanks for reading!

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.

CHECKDB Bug With SQL Server 2017 And ColumnStore Indexes

CHECKDB Bug With SQL Server 2017 And ColumnStore Indexes


Thanks for watching!

Video Summary

In this video, I delve into an interesting issue that still seems to be present in SQL Server up to version 2017 CU14, specifically when running DBCC CheckDB with the “physical only” option on databases containing columnstore indexes. While it’s not a catastrophic bug, encountering these errors can certainly be perplexing and time-consuming. I meticulously document my findings by rebuilding both the table and index, ensuring that all other potential issues are ruled out. The video walks you through how running DBCC CheckDB with physical only reveals numerous red text errors, which are confined to just one table within the database. Despite these error messages, a simple SELECT statement on the problematic table executes successfully without any issues, leading me to conclude that this might indeed be a bug rather than actual data corruption. I strongly recommend running a full DBCC CheckDB and DBCC CheckTable in addition to the physical only option for thorough verification.

Full Transcript

Howdy folks, Erik Darling here with Erik Darling Data, still, apparently that’s still a thing, trudging along all these many months later. And I’m recording this video basically to document what I think is still a bug in SQL Server up to SQL Server 2017 CU14. No, it’s not the most like, awful incriminating, like, like, like results destroying bug in the world. But it can be pretty confusing when you run into it. Because it happens when you run DBCC CheckDB with physical only on a database that has a columnstore index in it. Now, I’ve got to do some due diligence here. And I have rebuilt the table and I have rebuilt the index. And the version of SQL Server that I am running on is indeed 2014 CU14. This has been patched for a while now. This isn’t a fresh patching at all. This computer has been rebooted about a bajillion times since then. So there’s nothing like just waiting in the rings, like, just like, maybe he didn’t do something right. I’ve done most things right here. Now, when I run a regular CheckDB, when I run a full CheckDB on this database, right, the whole deal, not just physical only, this completes successfully. You can see down here that it ran for about, oh, gosh, there we go. About two minutes and 43 seconds. So that’s, that’s fine. That’s good. And when I run CheckTable on this, on this table, where the, where the problem happens, this also runs fine. So DBCC CheckTable on users underscore CX also completes successfully and doesn’t throw any errors. When I run DBCC CheckDB with physical only, I get a whole lot of red text. Yeah. And you can see that when we zoom in a little bit in here, you can see that all of the errors that it found are only in this one table. All right. And if we scroll through all the errors that it found are only in this one table, right?

All of the error, all the object IDs, that’s all this one seven, whatever, you can read it later, ends in 51. And the full message is looks like this. Offroad data is referenced by blah, blah, blah, blah, blah, blah, blah, blah, blah, blah, blah, blah, blah, blah, blah, blah, blah, blah, blah, blah, blah, not seen in the scan. Now, this matches exactly the text of a known issue that was supposedly fixed in cumulative update 11. We scroll in here, see you 11 2017. and if we read the error message that it references, this looks a whole heck of a lot like the one that we just saw in Management Studio. So this is still happening, and I mean it’s not the worst thing in the world. Again, just if you ever run into this, it’s a thing. Now if I take that number, this object ID right here, 17 yada yada, and I go look at what it is, it is indeed that table, users underscore cx, which is a copy of the users table in the Stack Overflow database that I have put a clustered columnstore index on, and we can see that the only index on this table right now is indeed that clustered columnstore index. I don’t have any other indexes sitting around on here. That is the whole kit and caboodle. Now what’s really funny, and what I think kind of proves that there’s no actual corruption in here, is that when I select star from that table, the query executes successfully. This runs for a while too, returning all those rows. 2.4 million rows, about 33 seconds, and so this runs for that whole time, and the query plan you can see reads, it scans that clustered columnstore index, so it’s not like sneaky reading data from anywhere else.

It’s all coming from right there. So all that to say this, if you are hitting error messages from running dbcc checkdb with physical only, and you’ve got columnstore indexes, don’t worry. I don’t think you actually have corruption. You might. You might. I don’t, but I think that that’s the bug that I’m hitting, and you might too. So to be extra safe, if you do find that physical only throws error messages on your columnstore indexes, run a full checkdb. Don’t just run with physical only.

Run dbcc checktable, run dbcc checkdb, check alloc, whatever file group, whatever else you might do. And maybe you’ll figure out if you do or don’t have corruption in that way. But for now, if you hit it, and you do your due diligence, and you check those other things, and you still find that you don’t have corruption, it’s only with physical only, well, join the club, pal. Yeah, it’s a bug. It’s a bug.

Fix your bug. Fix your bug. Anyway, again, I am Erik Darling with Erik Darling Data. I hope that… Is this how few people are using columnstore? It takes me to run checkdb with physical only to find a bug? That hurts. That hurts my feelings. I’m sure it hurts columnstore’s feelings, too. Anyway, 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.

SQL Server Index Key Column Order And Locking

Fall Guy


Let’s say we have a super important query. It’s not really important.

None of this is important.

SELECT   u.DisplayName, u.Reputation, u.CreationDate
FROM     dbo.Users AS u
WHERE    u.CreationDate >= DATEADD(DAY, DATEDIFF(DAY, 0, GETDATE()), 0)
AND      u.Reputation < 6
ORDER BY u.CreationDate DESC;

Maybe it’ll find users who created accounts in the last day who haven’t gotten any upvotes.

Shocking find, I know.

An okay index to help us find data and avoid sorting data would look like this:

CREATE INDEX ix_apathy 
    ON dbo.Users(CreationDate DESC, Reputation);

So now we know whose fault it is that we have this index, and we know who to blame when this happens.

Blocko


UPDATE u
SET u.LastAccessDate = GETDATE()
FROM dbo.Users AS u
WHERE u.Reputation = 147;
SQL Server Query Plan
Scanno
SQL Server sp_WhoIsActive Locks
Objectified

What’s going on here is that the optimizer chooses our narrower index to find data to update.

It’s helpful because we read far less pages than we would if we just scanned the clustered index, but the Reputation column being second means we can’t seek to rows we want.

The optimizer isn’t asking for a missing index here, either (okay, I don’t blame it for a query that runs in 145ms, but stick with me).

Switcheroo


If we change our index to have Reputation first, something nice happens.

To this query.

CREATE INDEX ix_whatever 
    ON dbo.Users(Reputation, CreationDate DESC);
SQL Server Query Plan
Sought
SQL Server sp_WhoIsActive Locks
Only Keys Now

With index order switched, we take more fine-grained locks, and we take them for a shorter period of time.

All That For This


If you have a locking problem, here’s what you should do:

  • Look at your modification queries that have WHERE clauses, and make sure they have the right indexes
  • Look at your modification queries that modify lots of rows, and try batching them
  • If your modification queries are horror shows, see if you can separate them into parts
  • If your critical read and write queries are at odds with each other, look into an optimistic isolation level

Thanks for reading!

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.

Deadlock Graph Frustrations In SQL Server

Money In The Bank


Capturing deadlock graphs has come a long way since… 2008? You used to need clunky trace flags, that for some reason would dump information line by line into the error log.

Nowadays, you can get a lot of great information from the system health extended event session, or use Extended Events (to wit, probably the most value I’ve gotten from Extended Events).

But no matter what you use to collect or display them, deadlock graphs can return some confusing information.

SELECTS!

Sure, you can have selects use lock hints that’ll make them more prone to deadlocks: serializable, repeatable read, XLOCK hints and all that. But that’s not really the point. The point is that it’s usually not just a select happening in a transaction that’s taking locks.

SQL Server Stored Procedure
Heh.

With a stored procedure like this, the deadlock graph can be weird looking.

Even though it’s the updates holding locks, the select queries show up as owners.

SQL Server Deadlock Graph XML
You’re not you.

And that brings me to the next oddity!

Cut Off Text!

This is also an “issue” with the plan cache. I get it — logging the gazillion lines of text you monsters cram into a single statement would be difficult — but most of the time i’d rather have everything from the FROM on than just a bunch of selected columns.

Sometimes, though, even short text gets cut off.

SQL Server Deadlock Graph XML
?_?

Where id = what?

Why is the o in “cornholio” gone?

I DON’T GET IT. WHERE DID IT GO.

Unresolved Procedures

You know, with this information, you can, like… Nevermind.

SQL Server Deadlock Graph XML
Dammit, janet

As if figuring out deadlocks isn’t hard enough, we now have the additional pleasure of digging this out ourselves.

Why? I don’t know.

Monkey In The Tank


None of this stuff is fun to deal with, even for people experienced with troubleshooting deadlocks.

If you’re just starting out, it can make figuring the situation out seem impossible.

Don’t worry though, I’m sure there are robots coming.

Thanks for reading!

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.

What Parameter Sniffing Looks Like In SQL Server’s Plan Cache

A Plan Has No Name


There are so many ways this question gets phrased.

  • Why is my query sometimes slow?
  • Why does restarting SQL Server make my query fast again?
  • Why does clearing the plan cache fix performance?

A while back, I put together a pretty good rundown of this on the DBA Stack Exchange site.

In the plan cache, it’s really hard to tell if a query is suffering from parameter sniffing in isolation.

By that I mean, if someone sends you a cached plan that’s slow, how can you tell if it’s because of parameter sniffing?

Cached Plans Lie


Here’s a simple example with a simple index:

CREATE INDEX ix_grindr 
    ON dbo.Users (CreationDate);

This query will return a couple days of data:

DECLARE @CreationDate DATETIME = '2013-12-30'
DECLARE @sql NVARCHAR(MAX) = 
N'
SELECT *
FROM dbo.Users AS u
WHERE u.CreationDate >= @iCreationDate
ORDER BY u.CreationDate DESC;
'

EXEC sys.sp_executesql @sql, 
                       N'@iCreationDate DATETIME', 
					   @iCreationDate = @CreationDate;
GO

This query will return a year and a couple days worth of data:

DECLARE @CreationDate DATETIME = '2012-12-30'
DECLARE @sql NVARCHAR(MAX) = 
N'
SELECT *
FROM dbo.Users AS u
WHERE u.CreationDate >= @iCreationDate
ORDER BY u.CreationDate DESC;
'

EXEC sys.sp_executesql @sql, 
                       N'@iCreationDate DATETIME', 
					   @iCreationDate = @CreationDate;
GO 

Time After Time


If we run those one right after the other, the query that returns a year of data will use the query plan of the query that returns a day of data.

This is what performance tuners call “bad”.

Let’s look at the cached plan!

SELECT ds.execution_count,
       ds.min_worker_time / 1000000. AS min_worker_time_ms,
	   ds.max_worker_time / 1000000. AS max_worker_time_ms,
	   ds.min_elapsed_time / 1000000. AS min_elapsed_time_ms,
	   ds.max_elapsed_time / 1000000. AS max_elapsed_time_ms,
	   ds.min_logical_reads,
	   ds.max_logical_reads,
	   ds.min_rows,
	   ds.max_rows,
       SUBSTRING(st.text, (ds.statement_start_offset / 2) +1,   
                 ((CASE ds.statement_end_offset  
                       WHEN -1 
				       THEN DATALENGTH(st.text)  
                       ELSE ds.statement_end_offset  
                   END - ds.statement_start_offset) / 2) +1) AS text,
	   dp.query_plan
FROM sys.dm_exec_query_stats AS ds
CROSS APPLY sys.dm_exec_sql_text(ds.plan_handle) AS st
CROSS APPLY sys.dm_exec_query_plan(ds.plan_handle) AS dp
WHERE st.dbid = DB_ID()
AND ds.query_plan_hash = 0x2D530BDF87345191
OPTION(RECOMPILE);

This query will go look at some details about the queries I ran.

Since my code is parameterized, the cached plan for both executions looks like this:

SQL Server Query Plan
So cute though

How could this plan cause any trouble?

Metric System


The plan isn’t telling us everything, though.

Look what those other columns have to say:

SQL Server Query Execution Details
Sometimes~

We have two executions, which we knew! We ran it twice.

But the min and max for many important metrics are very different.

CPU, wall clock time, reads, rows… They all tell me that this query has wild swings in performance.

On Your Own


One simple way to go about finding queries with parameter sniffing issues might be to just look for ones with much different CPU usage.

You could easily do with this other metrics; I just like CPU as a measure of these things.

SELECT TOP (100) ds.execution_count,
       ds.min_worker_time / 1000000. AS min_worker_time_ms,
	   ds.max_worker_time / 1000000. AS max_worker_time_ms,
	   ds.min_elapsed_time / 1000000. AS min_elapsed_time_ms,
	   ds.max_elapsed_time / 1000000. AS max_elapsed_time_ms,
	   ds.min_logical_reads,
	   ds.max_logical_reads,
	   ds.min_rows,
	   ds.max_rows,
       SUBSTRING(st.text, (ds.statement_start_offset / 2) +1,   
                 ((CASE ds.statement_end_offset  
                       WHEN -1 
				       THEN DATALENGTH(st.text)  
                       ELSE ds.statement_end_offset  
                   END - ds.statement_start_offset) / 2) +1) AS text,
	   dp.query_plan
FROM sys.dm_exec_query_stats AS ds
CROSS APPLY sys.dm_exec_sql_text(ds.plan_handle) AS st
CROSS APPLY sys.dm_exec_query_plan(ds.plan_handle) AS dp
WHERE st.dbid = DB_ID()
AND ds.execution_count > 1
AND (ds.min_worker_time / 1000000.) * 100. < (ds.max_worker_time / 1000000.)
ORDER BY max_worker_time_ms DESC
OPTION(RECOMPILE);

Thanks for reading!

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.

Why is SYSDATETIME() slower than SYSUTCDATETIME() In SQL Server?

Consider the following code that calls SYSDATETIME() 10 million times in a loop:

GO

CREATE OR ALTER PROCEDURE #p_local AS
BEGIN
	SET NOCOUNT ON;

	DECLARE @dummy DATETIME2(7), @loops INT = 0;

	WHILE @loops <= 10000000
	BEGIN
		SET @dummy = SYSDATETIME();

		SET @loops = @loops + 1;
	END;
END;

GO

EXEC #p_local;

On my machine the code takes about 11.6 seconds to execute. Replacing SYSDATETIME() with SYSUTCDATETIME() makes the code take only 4.3 seconds to execute. Why is SYSUTCDATETIME() so much faster than SYSDATETIME()?

It’s always a CPU problem


Both while loops drive a CPU core to 100% while executing. Within SQL server, elapsed time nearly equals CPU. For this code, I wouldn’t expect waits, latches, or spinlocks to reveal any clues. Query plans, execution stats, and other DMVs are unlikely to help as well. One thing that could help is information about the internals of SYSDATETIME() and SYSUTCDATETIME(). There’s a little bit on books online:

SQL Server obtains the date and time values by using the GetSystemTimeAsFileTime() Windows API. The accuracy depends on the computer hardware and version of Windows on which the instance of SQL Server is running. The precision of this API is fixed at 100 nanoseconds. The accuracy can be determined by using the GetSystemTimeAdjustment() Windows API.

Probably all of the useful information is likely locked behind the vault at Microsoft. Fortunately, both SQL Server and the guest OS are happy to give us information about what SQL Server is using its CPU to do if we have the right public symbols and ask nicely. I know of four primary ways of doing this:

  1. A True Professional could use WinDbg (the cool kids pronounce it “wind bag”) or another debugger to step through the code of both functions. I don’t have the skills or patience to do that.
  2. DBCC STACKDUMP or sqldumper.exe can be used to create a filtered memory dump on demand. The problem with this approach is that it only provides a single snapshot and our functions execute very quickly.
  3. Extended events can provide callstacks for many events. I’m not aware of an extended event that would fire at the right times for this code.
  4. ETW tracing can provide a summary of call stacks during a sampled time period. Common choices are Windows Performance Recorder or PerfView.

Option 4 is exactly what we need. We can run a single query at a time that performs uniform work and we want to see where CPU time is spent during the entire query’s execution. My tool of choice is PerfView.

Revealing SQL Server’s secrets


I used PerfView’s default settings and collected ETW profile data separately during both while loops. After resolving symbols and changing folding to 0, there’s a clear difference between SYSDATETIME() (on the left) and SYSUTCDATETIME() (on the right):

a40 perfview 1

kernelbase!GetTimeZoneInformation shows up with a high percentage for exclusive time for SYSDATETIME(). It does not show up at all for SYSUTCDATETIME(). In case it helps, here’s documention for a few of the column names:

Exc – The amount of cost (msec of CPU time) that can be attributed to the particular method itself (not any of its callees)
Exc % – The exclusive cost expressed as a percentage of the total cost of all samples.
Inc – The cost associated with this node as well as all its children (callees) recursively. The inclusive cost of the ROOT contains all costs.
Inc % – The inclusive cost expressed as a percentage of the total cost of all samples (will be 100% for the ROOT node)

sqltses!CXVariant::GetSysDatetime sounds an awful lot like SYSDATETIME() to me. It also shows up in the right place in the stack to be a match. Drilling into the methods are called by it:

a40 getsysdatetime

Note that the call to kernelbase!GetTimeZoneInformation takes up nearly all CPU time. There’s also a call to kernelbase!GetSystemTimeAsFileTime that barely shows up as well.

Sharp-eyed readers might be wondering if SYSDATETIMEOFFSET() results in calls to sqltses!CDatetimeOffset::SetSystemDatetimeOffset. The answer is yes, but this is left as an exercise for the reader.

Again, sqltses!CXVariant::GetSysUTCDatetime sounds like awful lot like SYSUTCDATETIME() to me. Drilling into the methods are called by it:

a40 getsysUTCdatetime

Note the low percentage of inclusive time for the call. SYSUTCDATETIME() is so cheap to call that most of the CPU time is spent executing the loop code instead of that function. kernelbase!GetTimeZoneInformation does not show up at all. There’s is still a call to kernelbase!GetSystemTimeAsFileTime that barely shows up.

This is probably an oversimplification, but I think it’s fair to say that each call to SYSDATETIME() needs to get time zone information. This results in a call to kernelbase!GetTimeZoneInformation that is relatively expensive. There is no need to call that method for SYSUTCDATETIME().

Does this matter? It depends™


Database folks like answering questions with “it depends”. Regrettably, they don’t always provide sufficient or accurate details about what it depends on. I strive to not fall into that trap. For many queries the performance difference between the two functions won’t matter. Consider the following two batches:

DROP TABLE IF EXISTS #BENCHMARK_TIME_FUNCTIONS;

CREATE TABLE #BENCHMARK_TIME_FUNCTIONS (SAVED_DATETIME DATETIME2(7) NULL);

INSERT INTO #BENCHMARK_TIME_FUNCTIONS WITH (TABLOCK)
SELECT TOP (10000000) SYSDATETIME()
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
CROSS JOIN master..spt_values t3
OPTION (MAXDOP 1);

GO

DROP TABLE IF EXISTS #BENCHMARK_TIME_FUNCTIONS;

CREATE TABLE #BENCHMARK_TIME_FUNCTIONS (SAVED_DATETIME DATETIME2(7) NULL);

INSERT INTO #BENCHMARK_TIME_FUNCTIONS WITH (TABLOCK)
SELECT TOP (10000000) SYSUTCDATETIME()
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
CROSS JOIN master..spt_values t3
OPTION (MAXDOP 1);

SQL Server doesn’t need to execute the datetime functions once per row. If it did, we’d expect a run time difference of about 7 seconds between the two queries. Instead, they execute in nearly exactly the same amount of time. I think that the details about guarantees of this behavior were found on a Connect (RIP) comment.

The performance difference can make a difference when the function is used to profile code that executes row by row. Suppose that we have related SQL statements and we want to measure the execution time of just one of the statements by calculating the time difference before and after execution. I’m not saying that this is the best way of doing this analysis, but I suspect it to be a common technique. Below is an example stored procedure with one copy using SYSDATETIME() and the other using SYSUTCDATETIME():

GO

CREATE OR ALTER PROCEDURE #USE_SUPERIOR_AMERICAN_TIME AS
BEGIN
	DECLARE @start_time DATETIME2(7),
	@end_time DATETIME2(7),
	@total_microseconds BIGINT = 0,
	@loops INT = 0;

	SET NOCOUNT ON;

	DROP TABLE IF EXISTS dbo.OFFICIAL_STATEMENTS;
	CREATE TABLE dbo.OFFICIAL_STATEMENTS (OFFICIAL_STATEMENT VARCHAR(30));
	
	WHILE @loops <= 50000 BEGIN INSERT INTO dbo.OFFICIAL_STATEMENTS VALUES ('NO COLLUSION');
		IF @@ROWCOUNT > 0	
		BEGIN
			SET @start_time = SYSDATETIME();

			INSERT INTO dbo.OFFICIAL_STATEMENTS VALUES ('NO OBSTRUCTION');

			SET @end_time = SYSDATETIME();

			SET @total_microseconds = @total_microseconds + DATEDIFF(MICROSECOND, @start_time, @end_time);
		END;

		SET @loops = @loops + 1;
	END;

	SELECT @total_microseconds;
END;

GO

EXEC #USE_SUPERIOR_AMERICAN_TIME;

GO

CREATE OR ALTER PROCEDURE #USE_INFERIOR_EUROPEAN_TIME AS
BEGIN
	DECLARE @start_time DATETIME2(7),
	@end_time DATETIME2(7),
	@total_microseconds BIGINT = 0,
	@loops INT = 0;

	SET NOCOUNT ON;

	DROP TABLE IF EXISTS dbo.OFFICIAL_STATEMENTS;
	CREATE TABLE dbo.OFFICIAL_STATEMENTS (OFFICIAL_STATEMENT VARCHAR(30));
	
	WHILE @loops <= 50000 BEGIN INSERT INTO dbo.OFFICIAL_STATEMENTS VALUES ('NO COLLUSION');
		IF @@ROWCOUNT > 0	
		BEGIN
			SET @start_time = SYSUTCDATETIME();

			INSERT INTO dbo.OFFICIAL_STATEMENTS VALUES ('NO OBSTRUCTION');

			SET @end_time = SYSUTCDATETIME();

			SET @total_microseconds = @total_microseconds + DATEDIFF(MICROSECOND, @start_time, @end_time);
		END;

		SET @loops = @loops + 1;
	END;

	SELECT @total_microseconds;
END;

GO

EXEC #USE_INFERIOR_EUROPEAN_TIME;

The second insert query takes a total of 4381219 microseconds with SYSDATETIME() and 4164658 microseconds with SYSUTCDATETIME(). That’s a 5% difference just from changing the datetime function. Of course, given that this code is measuring the difference of times SYSUTCDATETIME() is the correct choice because it remains accurate even if there’s a daylight savings switch during code execution. My own approach is trying to avoid running one-off stored procedures around 2:00 AM.

Final thoughts


There’s a large performance difference between SYSDATETIME() and SYSUTCDATETIME(). It isn’t necessary to guess which one is slower and why. ETW tracing tools such as PerfView can reveal exactly where the performance difference comes from. Thanks for reading!