Common Table Expressions are awesome because they let SQL Server reorder processing in whatever way it deems to be the most efficient for your current data distribution, on your current version of SQL Server. Default to CTEs.
When SQL Server gets that process wrong, switch to temp tables.
Let’s start with an example. I’m using SQL Server 2025 and the big Stack Overflow 2024-04 database in 2025 compat level.
The below query says, “Find the most popular Users.Location – the one with the most people in it – and then amongst the people who live there, find the top 250 users with the highest Reputation score.” It also creates a couple of indexes to help.
CREATE INDEX Location ON dbo.Users(Location);CREATE INDEX Reputation ON dbo.Users(Reputation);GOSET STATISTICS IO ON;WITH TopLocation AS (SELECT TOP 1 Location FROM dbo.Users WHERE Location <> '' GROUP BY Location ORDER BY COUNT(*) DESC)SELECT TOP 250 u.* FROM TopLocation tl INNER JOIN dbo.Users u ON tl.Location = u.Location ORDER BY u.Reputation DESC;
Does that query produce a good plan? Well, one of the ways we judge it is to look at its logical reads – in this case, we read 478,982 pages:
Is that a lot? Well, for comparison, here’s the size of the whole table:
That means in order to execute this query, SQL Server read more pages than there are in the entire clustered index of the table – despite the fact that we have indexes to help. If we look at the actual execution plan of the CTE query, we can see that SQL Server did indeed use the indexes:
Like I tell students in my Fundamentals of Query Tuning class, if there’s only one thing you take away from the entire class, let it be this: when you’re unhappy with a query’s performance, read the plan from right to left, top to bottom, looking for the first place where estimates versus actuals are 10x off or more.
In our example, as we read from right to left, the first thing that’s happening is the CTE. SQL Server is diving into the first non-null, non-empty-space Location in the Location index, aggregating them together to get counts of the number of people who live in each one, and then sorting them by that population count. At the time this sort finishes, SQL Server knows that it’s going to find 1 location – so far, so good, our estimates have been bang on:
So far, so good, our estimates have been bang on.
The next thing that happens is SQL Server dives into the Location index to find the people who live in that top 1 location. “Hey, SQL Server, how many rows do you think you’re gonna find?”
“FOURTEEN, BAWSS.”
“Uh, SQL Server, how many rows did you actually find for that location?”
“UH… MORE THAN FOURTEEN, BAWSS. SORRY, BAWSS.”
So as a result, it ends up doing 113,399 key lookups to get each user’s reputation. Each of those key lookups results in a few logical reads, and 113,399 * 3 reads each = big data reads, relatively speaking.
The problem: SQL Server knows we’re looking for just 1 location, but it doesn’t know what that location is, and what shocks most people the mostest, it doesn’t really put together the fact that we’re looking for the most-popular location. It thinks we’re looking for the average location. We need a way to help SQL Server to understand that the one location value we’re looking for is something special.
Back to our opening paragraph, I said start with a CTE – and if that isn’t getting the performance results you want, try substituting the CTE with a temp table:
DROP TABLE IF EXISTS #TopLocation;CREATE TABLE #TopLocation (Location NVARCHAR(200));INSERT INTO #TopLocation (Location) SELECT TOP 1 Location FROM dbo.Users WHERE Location <> '' GROUP BY Location ORDER BY COUNT(*) DESC;SELECT TOP 250 u.* FROM #TopLocation tl INNER JOIN dbo.Users u ON tl.Location = u.Location ORDER BY u.Reputation DESC;
Check the logical reads:
They’re down by 2/3! Let’s look at the execution plan to see what changed:
The first query is populating the temp table, same work as the CTE. That part was never the problem for logical reads, and the estimates were always great.
The second query is where things get spicy. SQL Server chose to scan the Reputation index backwards, from highest reputation to lowest, and for each person it found, it did a key lookup to check whether that person lives in our particular chosen location or not.
That sounds bizarre, but for locations that show up very often, this turns out to be very efficient! Note that the plan also doesn’t require a sort – the data’s already sorted by reputation descending – so there’s no sort in the plan, only a Top, which is like a cutoff valve that drops when we meet our 250 row goal.
In that second query plan, read the estimates vs actuals from right to left, top to bottom, and they’re muuuuuch closer than our CTE query was.
In this case, the temp table wins because:
However, this is only one case! I don’t want you drawing complete conclusions that temp tables are always better, because they’re not. In this specific example, I got a fresh execution plan based on the values I’m looking for, but that is not always the case. Even when it IS the case, you’re still dealing with what’s effectively OPTION (RECOMPILE), which can be a real pain in the processor.
So like I said in the beginning, Common Table Expressions are awesome because they let SQL Server reorder processing in whatever way it deems to be the most efficient for your current data distribution, on your current version of SQL Server. Default to CTEs.
But when SQL Server gets that process wrong, switch to temp tables.
To hear other peoples’ opinions on temp tables, check out the comments in this month’s T-SQL Tuesday invitation from Jeff Taylor. This month’s topic is temp tables, and as bloggers pour their hearts out, they’ll leave links over in the comments.
Today I’m hanging out in the dining room, or what would normally be a dining room, but we use it to store a 1992 Honda Beat. I explain why, and then take your top-voted questions from https://pollgab.com/room/brento. The audio on this one is a little tinny – the room is super-echo-y and I don’t think the camera picked up my lapel microphone. Sorry about that!
Over the years, tables – like your waistline – tend to get bigger. We keep tacking on more and more columns, one at a time, in order to handle app needs. It’s easier to add “just one more column” than it is to break things off into a whole separate table.
When you’re only handling a few rows at a time, like transactional insert/update/deletes and one-row selects, the overhead of these additional columns isn’t a big deal. SQL Server can dive into that one row and just fetch it, and since it sits on a single 8KB page anyway, the number of columns doesn’t affect single-row operations.
However, when you need to read multiple rows, the more rows you need to read, the more these extra columns will affect the overhead of the operation. This kinda thing is best illustrated with one of my Database Animations showing the difference between an index that only has Id and DisplayName, versus one that includes a bunch of wider string columns:
▶ Watch the animated version of Wide Rows Seek Vs Scan
I am amused by the AI’s final comment: “wide columns ride free on seeks.” Alrighty then. That certainly sounds like something I’d say. (I use Claude Code to build these animations: we storyboard them out together, and then it handles the details, and surprises me with little tidbits like that.)
As your rows get larger and larger – either due to more columns, or wider columns like JSON and XML, or both – SQL Server is forced to keep an eye on each row’s length. If the row can’t fit on an 8KB page, SQL Server automatically moves that data off-row.
As long as you’re not touching that off-row column – like if you’re not selecting or updating it – then the off-row column doesn’t impact the number of reads you need to do. That’s pretty cool, and it means that I don’t mind if people just store JSON data without manipulating it, and they only fetch it when they need it.
If you wanna be proactive, and if you’re sure that most operations don’t need those large columns, you can even tell SQL Server that you want large columns stored off-row by default, even when the row sizes are small. Check out sp_tableoption:
EXECUTE sp\_tableoption 'dbo.Users', 'large value types out of row', 1;
Let’s get animated. On the left side, we have a table where all the wide columns stay on-row, and on the right, we’ve used sp_tableoption to force them all off-row, onto their own pages linked by a pointer:
▶ Watch the animated version of Off Row Storage
As long as you’re not selecting *, this option makes more sense, especially for big string columns like JSON, XML, and (N)VARCHAR(MAX) that you only grab when you’re pulling specific individual rows out of the database. The Users.AboutMe column is a great example: we ain’t running reports on AboutMe contents, nor using it for filtering, just outputting it when rendering a specific user’s profile page.
That sp_tableoption setting only takes effect on newly inserted/updated rows. If you want it to apply to the stuff that’s already in the tables, you’ll need to do an index rebuild.
Is your company hiring for a database position as of August 2026? Do you wanna work with the kinds of people who read this blog? Let’s make a love connection.
If your company is hiring, leave a comment. The rules:
If your comment isn’t relevant or smells fishy, I’ll delete it. If you have questions about why your comment got deleted, or how to maximize the effectiveness of your comment, contact me.
Each month, I publish a new post in the Who’s Hiring category here so y’all can get the latest opportunities.
Every SQL Server performance tuner eventually faces the same question: is this normal, or is something wrong?
The answer starts with 3 numbers about your SQL Server:
On August 19, I’m doing a free webcast with JAMS Software to show you exactly where to find those numbers and what a “normal” range looks like for each one. Register now.
It’s official: SQL Server 2022 was the last release for SSRS.
At SQLBits this week, Microsoft announced that SQL Server 2025 won’t include SSRS.
Instead, all SQL Server 2025 customers will get free licensing for the on-premises Power BI Report Server (PBIRS.) The same license key you use to install 2025 will also work to activate PBIRS.
Microsoft’s got links on how it’ll work:
Like any transition, there’s going to be manual work involved. Some data sources aren’t supported, and some SSRS features (like linked reports) aren’t supported. It reminds me of the DTS-package-to-SSIS conversion years ago, which even spawned small consulting companies that focused exclusively on that kind of work because there was so much of it. This transition will keep Microsoft partners busy for a few years.
I don’t do any reporting work, but I think the change makes complete sense for Microsoft and for folks who want better reporting. The change log for Power BI Desktop and for Power BI Report Server make it clear that Microsoft’s been investing way more in those products, whereas SSRS hasn’t gotten any love in years. I have fond memories of SSRS from 20 years ago, back when it came out and quickly decimated the market share of Crystal Reports. However, the writing’s been on the wall for quite a while. Power BI’s where the action is.
So, does this mean the gradual end of the free-in-the-box BI bundle that emerged in 2005: SQL Server Analysis Services, Integration Services, and Reporting Service? Those tools have all steadily declined in usage. Well, SSAS gets improvements in SQL Server 2025, but SSIS’s 2025 changes seem to be more about removing stuff than adding it. Might SSIS be next on the chopping block?
My guess is that SSRS’s ending was easier because Microsoft already had a fully on-premises solution in PBIRS. However, fully on-premises folks don’t have a Microsoft solution to replace SSIS yet. The closest thing they have is a self-hosted integration runtime, but the control is still dependent on the cloud. Announcing the ending of SSIS would be harder if Microsoft doesn’t have an alternative, so rather than ending it outright, they’re just stripping away stuff they don’t want to support (like Attunity and Hadoop.) Given that SSIS gets much less attention from end users (as opposed to reporting apps), Microsoft will probably let SSIS soldier on with zero development effort, like Service Broker. If it works for you, great – keep using it! But just know that the writing’s on the wall for this one, too.
I’m on a Med cruise, and I stopped the boat today in Corsica to make sure your questions from https://pollgab.com/room/brento got the answers they deserve. I started by mentioning that I’m using a 360 degree camera, but the file size on that video was so huge that I couldn’t upload it from the cruise ship, so you just get the 180 version.
Here’s what we covered:
How often do you run backups, corruption checking, index maintenance, and statistics updates?
Click here to take the anonymous poll.
After you fill it out, you’ll be able to see the average responses from other folks. I’ll circle back next week and share the answers, plus chime in with my thoughts.
Update – I’ve closed comments on the post to make it clear that we’re doing a poll here, not asking for text answers, so we can deal with data instead of feelings. WINK WINK
Indexing can make or break your SQL Server performance. You need just the right balance: enough indexes to speed up your queries, but not so many that they drag down your inserts, updates, and deletes.
Join me as I introduce the “5 & 5 indexing guideline” — a practical approach to tuning indexes for maximum efficiency and minimum contention. Through real-world demos, I’ll will show how a single poorly chosen index can bring your system to a crawl with blocking issues.
Whether you’re troubleshooting slow transactions or proactively tuning your SQL Server for performance, this session will give you actionable strategies you can use immediately.
Who should attend? DBAs, SQL Developers, Database Engineers, and anyone responsible for SQL Server performance.
You’ll learn:
Register here for this free webcast sponsored by Idera. See you there!
I’ve always wanted to see the Barcelona Pavilion by Mies van der Rohe, and by happy coincidence, I ended up walking right past it while visiting Barcelona! It’s one of the most important beginnings of modern architecture, and I loved it. I brought you along with me and talked through your top-voted questions from https://pollgab.com/room/brento.
Here’s what we covered:
There’s a secret theme to the questions I hand-picked from the queue at https://pollgab.com/room/brento. Normally I just take the top-voted ones, but today, see if you can spot the thing they all have in common:
Here’s what we covered:
SQL Server Management Studio v21 Preview 1 is out and let’s give it a quick whirl. It’s installed with the Visual Studio installer. That doesn’t mean Visual Studio is installed or required – it’s not – it’s just that the SSMS team is leveraging the work that the VS folks have put in over the years.
After installing, you’re prompted to sign in to sync your SSMS settings across devices, but there’s a Skip link. I do wish there was a “skip this forever” link, but whatever:
If you log in with Github, there’s a rather spooky-looking list of permissions that SSMS gets:
I’m a little suspicious as to why SSMS needs all that. Insert joke about least-privileged-permissions here. Anyhoo, after SSMS starts, to enable dark mode, click Tools, Options, put dark in the search box, and choose Dark in the Color Theme:
Presto! Dark mode:
Eagle-eyed readers will notice that is not exactly dark mode. Erin Stellato (the SSMS PM) asked y’all for patience as they gradually work through the screens, converting them to dark mode. Once you’re connected, things look better:
The thing over at the far right was in the old SSMS too, it was just disabled by default. It lets you navigate through long stored procedures easier. At the bottom right, there’s an “Add to Source Control” link, which doesn’t do anything for me despite being logged in with Github. I’m guessing I have to organize a project or something first – I’ll dig into that later.
Query plans are not in dark mode yet:
To get into source control, click Git, clone a repository. I’ll take the First Responder Kit for example:
And a few seconds later:
Emotional damage. Okay, maybe it’s because I just copy/pasted the URL straight from Github. I edited out the /tree/main part at the end of the URL, and the second time it appeared to go through, although the SSMS UI didn’t change other than now the title bar says ViewPickerAutoload:
Which is honestly awesome, because I loved the pickers, and anything that SSMS can do to help Mike, Danielle, and Frank load the truck, the better:
I’m just kidding, of course. Frank passed away a couple of months ago. If only he’d been alive to see the show grace the title bar of SQL Server Management Studio.
Look, the point is that SSMS v21 is still very, very much in preview. It will install side-by-side with the legacy version (ha ha ho ho), so you can use both. v21 doesn’t support Analysis Services, Integration Services, or maintenance plans, so if you rely on any of that, you’ll still need the old v20. There are also a list of known issues with the preview, and right now I’d say it’s still for us hard-core early adopters.
You are not ready for how weird my take is.
Last week at the PASS Summit conference in Seattle, Microsoft showed off the upcoming SQL Server Management Studio v21.
One of the most intriguing features is that it has Copilot built in.
No, not Github Copilot, that’s a different thing. No, not the Copilot in Visual Studio, either. No, not the Copilot that’s in the Azure portal either. SSMS Copilot is yet another version of the same basic concept: a text box where you can ask questions, and AI uses contextual awareness of your database and your query window in order to answer those questions. I don’t just like this feature, I adore it, because I already use AI every single day to get my job done. (This blog post is AI-free, as most of my posts are, but I use it for all kinds of stuff like T-SQL code review and refactoring.)
The Microsoft staff demoing SSMS Copilot at Summit (Erin Stellato, Bob Ward, Anna Hoffman) were very quick to point out its limitations:
And that last part is where the wheels come off.
To understand the problem,we gotta revisit history.Remember when Books Online first came out? Of course you don’t, dear reader, because you’re young and attractive. But being one of the olds, let me tell you how it went.
We used to get the documentation in printed format, and then Microsoft began distributing it in electronic form as part of the installer. You could just hit F1, and browse the documentation in SSMS. Later, the documentation moved into HTML, and later still, the primary home for the documentation became Microsoft’s web site. Some users were horrified when they’d hit F1, and a browser built into SSMS would take them to Microsoft’s web site to see the most recent version of the documentation.
Eventually, people just stopped using F1, and they used Google. The limited browser built into SSMS was garbage, and it didn’t support the kinds of stuff modern users wanted to do, like view documentation sites that require Javascript, like Stack Overflow.
The web browser is still there in SSMS, but I can’t remember the last time I used it. It’s just too limited. And that’s what the problem is going to be with Copilot in SSMS.
The SSMS Copilot demos made it glaringly obvious.The demos consisted of:
Which, uh, is exactly what you can do with ChatGPT and other LLMs today, and I’ve been doing for months. You don’t have to wait. Go get started now. You can pick other LLMs if you like, including local ones if you’re paranoid about sending your company’s database schema and queries up to the cloud. You can use the latest and greatest cutting edge LLM models, way beyond what SSMS Copilot supports. You can send query plans up there for analysis.
Copilot in SSMS, when it eventually ships, is going to lag far behind – just like SSMS’s web browser does.
Oh sure, SSMS Copilot absolutely does have some advantages over copy/pasting stuff into ChatGPT, like automatically picking out which tables & indexes are relevant to your question, and running diagnostic queries on your server to fetch the metadata on that table or index. But those advantages seemed so small to me, especially given how long it’s going to take to get that product live – compared to just getting started right now with ChatGPT or your LLM of choice.
Don’t get me wrong: Copilot in SSMS is still a good idea, Just like Books Online distributed via local HTML files was a good idea way back when. It was a stepping stone, and Microsoft had to do it at the time, just like they have to put AI in all their developer tooling today. I just don’t think it’s the final AI product that we’re all going to settle on.
So what could be better?Here’s my prototype for a version of SSMS that includes AI:
I can hear your confused voice from here: “Wait, Brent, that looks just like today’s version of SSMS. You type what you want in the top pane, hit execute, and what you want comes out of the bottom pane.”
Exactly!
Why should users have to jump back and forth between two different input areas? Just type what you want!
That simple, intuitive approach is harder to build in 2025. It’s much easier for Microsoft to ship Copilot as a separate SSMS extension, duct taped into a separate tab, and force users to copy/paste stuff back and forth between different text boxes and result sets. Copilot will even catch on temporarily in 2025-2026, just as the SSMS browser did, and you will definitely see me using it.
I’m just excited and looking forward to whatever comes next to replace it.
That face I’m making in the video thumbnail is priceless. YouTube randomly picks these, and I laughed out loud at this one. Anyhoo, let’s get to answering your top-voted questions from https://pollgab.com/room/brento:
Here’s what we covered:
I took the Graffiti Gulf 356 out to the Valley of Fire State Park to exercise it, and took your top-voted questions from https://pollgab.com/room/brento.
Here’s what we covered:
I’m on a boat! We took an Alaska cruise with some friends aboard the Norwegian Jewel. En route to Icy Strait Point, Alaska, I took your top-voted questions from https://pollgab.com/room/brento.
Here’s what we covered:
And that zipline that I mention in the video? Yeah, that was pretty awesome.
View this post on InstagramA post shared by Brent Ozar (@brento)
Is your company hiring for a database position as of October 2024? Do you wanna work with the kinds of people who read this blog? Let’s set up some rapid networking here.
If your company is hiring, leave a comment. The rules:
If your comment isn’t relevant or smells fishy, I’ll delete it. If you have questions about why your comment got deleted, or how to maximize the effectiveness of your comment, contact me.
Each month, I publish a new post in the Who’s Hiring category here so y’all can get the latest opportunities.
I recently asked you to leave a comment with your biggest database regret, and the comments were great! Here were my favorites:
Runners-UpThese 5 folks each won a Fundamentals Bundle lifetime access valued at $695:
Thirster42: writing code to help a team dynamically create sequences. they didn’t tell me they were going to generate millions of them so now the db is super hard to open in object explorer, run sql compare, or use auto complete tools.
Thirster42’s was literally the very first comment, and I laughed out loud when it came in. It’s such a good example of a regret. In theory, sure, you can dynamically create just about anything in SQL Server, but the more of them you create, the worse shape you’ll be in. It reminds me of Swart’s 10 Percent Rule.
Stefano: One of my first jobs as a consultant: critical production SQL Server with performance issues (slow queries, blocking), high workload (thousands of queries/s). To study the problem I decided to run a server-side trace. The idea was to capture a lot of types of events and not lose any records… Result: total block. No way to stop the task. Flurry of calls from customers… Not knowing what to do, I had to restart the server remotely..
I’ve been there too. I do wish there were more “are you sure you wanna do this” guardrails built into T-SQL commands. Those kinds of guardrails are common in GUIs, and in operating system text commands, but completely unseen in query language commands. (“Hey, are you sure you wanna run a delete without a where clause?”)
Alex: No pure SQL compatibility between different products. MSSQL, Oracle, PostgreSQL etc. TOP(100), LIMIT(100), FIRST(100).
At first when I read Alex’s answer, I thought to myself, no, Alex, this contest was supposed to be regrets about things YOU had done, but when I went back and reread the post, I realized Alex’s answer was totally legit. Better than that, Alex’s answer is one of my OWN top regrets about the database industry too, come to think about it! The languages are juuuuust close enough to be annoying, and just close enough that execs think that a migration will be easy. It will not.
SmartWombat: My first ever FORTRAN computing assignment at college (’67) just to input and store name, age, sex. Report on the invalid data. Next day I went back to the lecturer and said there’s nothing in the data to determine if they are an invalid or not. Cue red face when he explained it to mean incorrect data, as in not valid. Not data about invalids. Lesson learned on requirements gathering at a very early stage !
I laughed out loud at this one too because it took me a couple of reads to understand what “invalid” meant in this context.
Thomas Franz: On a previous job my coworker decided to not allow NULLs in from/to columns and to use a default value instead. Sadly he decided it to be 1899-12-31 for both, from and to, so you always had to write something as GETDATE() BETWEEN t.valid_from AND IIF(t.valid_to = ‘18991231’, ‘20991231’, t.valid_to)
Uuuuuuugh. Yeah, that would suck.
Grand Prize WinnerChris Wilson: My biggest regret is not starting a online presence. For over a decade I’ve followed the careers of many in the SQL community and thought I could do that and had grand plans to do so but never pulled the trigger. Like planting a tree the best time to start a SQL blog is 20 years ago. The second best time is today. I don’t know that it would have changed my career in any meaningful way but I do know it would have solidified my own knowledge in a very tangible way. “The best way to learn is to teach” as the old saying goes.
I felt guilty as soon as I read Chris’s regret because I knew I had to give him the prize, which happens to be a Fundamentals + Mastering Bundle lifetime access valued at $2,495.
I don’t care what your online presence is: a Github repo, a YouTube channel, a blog, a podcast, whatever – but pick one thing that works for you, and start doing it.
Get ‘er done.
Your accounting officeEvery year during November, we run a big sale all month long.
And every year, right after the sale ends, we get a bunch of panicked emails from people screaming, “I HAD NO IDEA YOU WERE RUNNING A SALE, I NEED MORE TIME, YOU HAVE TO EXTEND IT SO I CAN GET APPROVAL FROM MY 14 DIFFERENT DEPARTMENT HEADS AND GET A CHECK SENT BY BOAT FROM OUR ACCOUNTING TEAM IN TRISTAN DA CUNHA!” Followed by a lot of sad trombone noises.
This year, let’s make it a little easier by giving you some advance notice.
Here are the deals we’ll be running during November:
And if you want to add on our apps, SQL ConstantCare® and the Consultant Toolkit, these bundles include those too:
So now’s the time to coordinate with your manager. Here are a few questions that managers usually ask:
Can we pay via check or purchase order? Yes, but only for 10 or more seats for the same package, and the full payment (not just a purchase order or IOU) must be received before the sale ends. Email us now at Help@BrentOzar.com with the package you want to buy and the number of seats, and we’ll send you a quote to include with your check. Make your check payable to Brent Ozar Unlimited and mail it to 9450 SW Gemini Drive, ECM #45779, Beaverton, OR 97008. Your payment must be received before we activate your training, and must be received before the sale ends. Payments received after the sale ends will not be honored. We do not accept POs as payment unless they are also accompanied by a check in the same envelope. For a W9 form: http://downloads.brentozar.com/w9.pdf
Can we get discounts for group buys? Not during the Black Friday sale. These prices are as low as we go.
Can we send you a form to fill out? No, to keep costs low during the Black Friday sales, we don’t do any manual paperwork or sign up for your vendor system. To get these awesome prices, you’ll need to check out through our web site and use the automatically generated PDF invoice/receipt that gets sent to you via email about 15-30 minutes after your purchase finishes. If you absolutely need us to fill out paperwork, we’d be happy to do it at our regular (non-sale) prices – email us at Help@BrentOzar.com for details.
Sales will start November 1. See you there!
I took your top-voted questions from https://pollgab.com/room/brento while recovering from a late night of partying.
Here’s what we covered:
Brent, Big Coat EditionYou’ve watched my free How to Think Like the Engine class that focuses on the small 1GB Users table.
Let’s take things up a notch by using the ~164GB Posts table full of questions & answers. You’ll learn:
Join me next Thursday for a free webcast sponsored by Pure Storage. See you there!
About 10 years ago, Microsoft made changes to the Cardinality Estimator (CE) which caused some problems for SQL Server upgrades. When folks upgraded to SQL Server 2014, they also casually switched their databases’ compatibility level to the latest version, because for years that hadn’t really affected query plans. They just figured they wanted the “latest and greatest” compat level, without regard to the effects. That backfired badly when they suddenly got 2014’s Cardinality Estimation changes.
So for several years, whenever someone upgraded from older versions, and they complained about performance, the stock community answer was, “Change your compatibility level back to what it used to be.” In many cases, that just solved the problems outright, leading to blog posts like this and this.
Even today on SQL Server 2019 & 2022, this advice is still relevant! If you mess around with compatibility levels, you can absolutely change cardinality estimations in ways you didn’t expect.
One amusing example is SQL Server 2022’s cardinality estimation feedback. Even if your workload has been on 2014+’s “new” Cardinality Estimator for a while, 2022’s CE feedback can change cardinality estimations back to older versions! That’s a complex example, though, and I’d rather stick to simple examples for here on the blog.
Simple Example Where 2022’s CE ImprovedLet’s say our code uses a scalar function that just implements an RTRIM:
CREATE OR ALTER FUNCTION dbo.CustomTrimmer (@Input NVARCHAR(40)) RETURNS NVARCHAR(40)ASBEGIN DECLARE @Output NVARCHAR(40) = RTRIM(@Input); RETURN (@Output);ENDGO
Starting with SQL Server 2019, SQL Server can attempt to inline that scalar function. Not only does that affect the shape of the execution plan, but it also affects the estimates.
Let’s see it in action with the largest Stack Overflow database. We’ll start by building an index to support our query, and then we’ll run the same query in two different compatibility levels – first 2016, then 2022:
CREATE INDEX DisplayName ON dbo.Users(DisplayName);GOALTER DATABASE [StackOverflow] SET COMPATIBILITY\_LEVEL = 130 /* 2016 */GOSELECT TOP 101 * FROM dbo.Users WHERE dbo.CustomTrimmer(DisplayName) = 'alex' ORDER BY Reputation DESC;GOALTER DATABASE [StackOverflow] SET COMPATIBILITY\_LEVEL = 160 /* 2022 */GOSELECT TOP 101 * FROM dbo.Users WHERE dbo.CustomTrimmer(DisplayName) = 'alex' ORDER BY Reputation DESC;GO
The actual query plans:
First off, the good news is that 2022 absolutely smokes 2016 simply because it inlines the function. The query can complete in about 1 second, as opposed to 2016’s 1 minute 16 seconds. But set that aside for a second and let’s look at the cardinality estimation changes.
The top plan (2016) does a clustered index scan because SQL Server 2016 made a hard-coded assumption that 10% of the rows would match our filter. The first operator (top right) estimated that 22,484,200 rows would come out of the clustered index scan, and then the second operator (the filter) estimated that 2,248,420 rows would match.
Gotta love those hard-coded estimates at exactly 10% because they’re easy to spot, but I do wish they had a yellow bang on ’em so that SQL Server would warn us that it’s making up estimates out of thin air.
The bottom plan (2022) uses the index AND it gets absolutely beautiful, bang-on cardinality estimates. The first operator (top right, index scan) estimated 18,882 rows would come out.
The haters will say, “Brent, that’s not fair, that’s a different feature and it’s not the Cardinality Estimator.” They’re wrong, of course: the important part is that things have been changing all around the Cardinality Estimator, changing its inputs, and so as a result it’s common to see different estimations coming out of the CE with each subsequent version.
Simple Example Where 2022’s CE WorsenedIn the first example, our simple function used the exact same NVARCHAR(40) that our table’s DisplayName column uses. However, what happens if we change the function to use a more generic datatype, like NVARCHAR(MAX)?
CREATE OR ALTER FUNCTION dbo.CustomTrimmer (@Input NVARCHAR(MAX)) RETURNS NVARCHAR(MAX)ASBEGIN DECLARE @Output NVARCHAR(MAX) = RTRIM(@Input); RETURN (@Output);ENDGO
We’ll run our queries again, and look at their actual execution plans:
Now, SQL Server 2022 ignores the index. Why would you ignore the index in a query like this? Because you estimate that too many rows will come back, making the key lookup too expensive. But… look more closely at 2022’s execution plan. How many rows did it actually think were going to come out after the function’s filter?
Just one.
I have no idea what the sam hell bug this is, or which part of the engine it’s in – whether it’s the CE or scalar function inlining or the query optimizer. All I can say is that the estimate is hot garbage. As a result, the sort spills to disk because SQL Server didn’t allocate enough memory to sort the rows that actually came back.
So you can’t even say, “Thanks to 2019, scalar functions get inlined, so estimates are more accurate.” They’re not. They’re all over the place, even in simple cases like this where we’re just changing the length of a datatype. (Again: the example here in the blog post is purely to talk about cardinality estimation, but at least 2022’s query runs way faster in this case because the function gets inlined.)
The Moral of the StoryThe moral of the story: keep your grubby fingers off the compatibility level switch until you’ve followed my migration instructions.
To learn more about topics like this, attend my PASS Summit pre-conference workshop in Seattle, Tuning T-SQL for SQL Server 2019 and 2022.
If you’re using TRY/CATCH to do exception handling in T-SQL, you need to be aware that there are a lot of things it doesn’t catch. Here’s a quick example.
Let’s set up two tables – bookmarks, and a process log to track whether our stored proc is working or not:
DROP TABLE IF EXISTS dbo.Bookmarks;DROP TABLE IF EXISTS dbo.ProcessLog;CREATE TABLE dbo.Bookmarks( URL VARCHAR(50));GOCREATE TABLE dbo.ProcessLog( ProcessDate DATETIME, StatusMessage VARCHAR(50));GO
And create a simple stored procedure that adds a bookmark, and tracks whether it was successful:
CREATE OR ALTER PROC dbo.AddBookmark @URL VARCHAR(50) ASBEGIN BEGIN TRY INSERT INTO dbo.Bookmarks VALUES (@URL); INSERT INTO dbo.ProcessLog VALUES (GETDATE(), 'It Worked'); END TRY BEGIN CATCH INSERT INTO dbo.ProcessLog VALUES (GETDATE(), 'It Failed'); END CATCHENDGO
When you execute the proc, it succeeds, and a row is written to ProcessLog:
But if someone adds a new column to our Bookmarks table:
ALTER TABLE dbo.Bookmarks ADD BookmarkedOn DATETIME;
And we try to run our stored proc again, it fails:
Because the stored proc’s insert statement didn’t explicitly list the columns in the Bookmark table:
INSERT INTO dbo.Bookmarks VALUES (@URL);
Okay, that’s bad code – but did the CATCH come into play? Check the table contents:
There’s no row in ProcessLog saying that the process failed! What happened? Wasn’t our CATCH supposed to insert a row there?
Catch only catches SOME errors.Early errors aren’t caught, like errors when the query is being compiled. In this case, when SQL Server was building an execution plan for the stored procedure, SQL Server couldn’t build a valid execution plan because there’s no way for it to execute the insert. The compilation failed, which technically means the query wasn’t executed – even though to you and me and our app users, it was executed.
Low-priority errors aren’t caught, like under severity 10. Those are just considered informational messages.
High-priority errors aren’t caught, like severity 20 or higher. Those terminate the connection altogether.
Right about here is where you’re expecting me to give you a magic bullet that fixes these problems, but instead, I have to give you a monster amount of documentation. Check out the epic posts Error and Transaction Handling in SQL Server Part 1, Part 2, and Part 3 by Erland Sommarskog. They’re monster posts, and I have to be honest with you, dear reader, I haven’t ever read them cover to cover. This is one of those times where I’m glad I have a fake job, aka consultant, where I can just say, “If our stored procedure’s business logic is really that critical and complex, it’s time we move that processing over into an application language like C# that has better error handling and testability.”
I went through your top-voted questions from https://pollgab.com/room/brento plus hit live ones from the TikTok viewers.
Here’s what we covered:
Is your company hiring for a database position as of September 2024? Do you wanna work with the kinds of people who read this blog? Let’s set up some rapid networking here.
If your company is hiring, leave a comment. The rules:
If your comment isn’t relevant or smells fishy, I’ll delete it. If you have questions about why your comment got deleted, or how to maximize the effectiveness of your comment, contact me.
Each month, I publish a new post in the Who’s Hiring category here so y’all can get the latest opportunities.
Today’s Office Hours had a ton of technical difficulties because it was my first live stream on both Twitch and TikTok. The first few minutes were cut off, and you can’t see the questions onscreen. Nonetheless, there are good answers in here, so let’s do it:
Here’s what we covered:
In this week’s Query Exercise challenge, I explained SQL Server’s 201 buckets problem. SQL Server’s statistics only handle up to ~201 outliers, which means that outliers ~202-300 get wildly inaccurate estimates.
In our example, I had an index on Location and perfectly accurate statistics, but even still, this query gets bad estimates because Lithuania is in outliers ~202-300:
CREATE INDEX Location ON dbo.Users(Location);GOSELECT * FROM dbo.UsersWHERE Location = N'lithuania'ORDER BY Reputation DESC;
SQL Server estimates that only 8 rows will be found for Lithuania, when in reality 2,554 rows come back:
This under-estimation isn’t really a problem for this particular query’s performance, but when I’m teaching you concepts, I gotta teach you how the simple things break before I start getting into real-world specifics. If you can’t solve this simplistic version, then there’s no way you’re gonna be able to solve the real-world version, which we’re gonna get into in the next Query Exercise.
Solving It with a New Filtered StatisticI cringe when I type that heading out because it’s such a slimy solution. I’ll start with a simple version, and it’s actually something I hinted at you not to do in the challenge instructions because it’s a bad idea, but bear with me for a second.
SQL Server lets you create your own statistic on specific outliers by using a statistic with a WHERE clause, like this:
CREATE STATISTICS Outliers ON dbo.Users(Location) WHERE Location = N'lithuania' WITH FULLSCAN;GOSELECT * FROM dbo.UsersWHERE Location = N'lithuania'ORDER BY Reputation DESC;
The query gets a new execution plan with bang-on estimates:
Okay, great – but like I mentioned in the challenge requirements, you wouldn’t actually do this in the real world because you’d probably have multiple outliers, not just 1. If you only had 1 big outlier, then… it’d be in your regular 201 buckets!
Instead, if we’re going to solve this with a filtered statistic, we need to create our own manual statistic for the next 200 outliers. First, let’s write a query to find outliers 201-400. Keep in mind, I’m not guaranteeing that they’re not in the existing Location statistics yet – I’m just trying to illustrate how we would find Locations with a large population, but that aren’t in the top 200:
SELECT CAST((N'N''' + Location + N'''') AS NVARCHAR(MAX)) AS Location FROM dbo.Users WHERE Location IS NOT NULL AND Location <> '%''%' /* Exclude ones with a ' in it, since that'd break our dynamic SQL */ GROUP BY Location ORDER BY COUNT(*) DESC OFFSET 200 ROWS FETCH NEXT 200 ROWS ONLY
To keep things simple, I’m not using a tiebreaker here. (There are only so many things I can cover in a blog post, and at the end of the day, remember that the focus here is on statistics & outliers.)
Next, let’s use that to build a string that will create our filtered statistic:
DECLARE @StringToExecute NVARCHAR(MAX);WITH OutlierValues AS ( SELECT CAST((N'N''' + Location + N'''') AS NVARCHAR(MAX)) AS Location FROM dbo.Users WHERE Location IS NOT NULL AND Location <> '%''%' /* Exclude ones with a ' in it, since that'd break our dynamic SQL */ GROUP BY Location ORDER BY COUNT(*) DESC OFFSET 200 ROWS FETCH NEXT 200 ROWS ONLY)SELECT N'CREATE STATISTICS Location\_Outliers ON dbo.Users(Location) WHERE Location IN (' + STRING\_AGG(Location, N',') + N') WITH FULLSCAN;' AS DynamicSQLFROM OutlierValues;
Which gives us a string we can execute, and I’ve highlighted Lithuania just to show that it’s there:
Note that my dynamic SQL is not checking for the existence of the stat and dropping it – if you were going to productize a solution like this, that’s left as an exercise for the reader. You could either drop & create the statistic on a regular basis (like, say, quarterly – the top outliers shouldn’t change that much in a mature database) or create a new filtered state with a date-based name, and then drop the old one. That’s much more work though.
For now, let’s create the statistic and check out its contents:
We now have a bucket dedicated exclusively to Lithuania. Try the Lithuania query filter again, and look at the new actual query plan:
Presto, Lithuania gets accurate estimates, as do any of the other top 200 values.
To see why, right-click on the SELECT operator, click Properties, and go into OptimizerStatsUsage. Both the Location and the Location_Outliers statistics were used when building the query plan’s estimates.
Like I said when I first started talking about this solution above, this solution makes you cringe. It feels dirty, like it’s made out of chewing gum and duct tape. This feels like something that should have a more intuitive solution. For example, over in the free database crowd, Postgres lets you set the number of buckets at the server level with default_statistics_target, and lets you override it with an ALTER TABLE SET STATISTICS command. Postgres lets you pick up to 10,000 outliers – and hell, SQL Server’s filtered statistics solution only gives you another 200 per filtered stat that you create!
At the same time though, the vast majority of tables will never grow large enough to experience this problem. In this blog post, I’m illustrating the problem with the Users table in the most recent Stack Overflow database, and that table has over 22 million rows, with over a decade of activity for a very large web site. Even still, the query we’re talking about doesn’t even have a performance problem!
It’s also important to note that both the 201 buckets problem and the filtered statistics solution have nothing to do with parameter sniffing, trivial optimization, plan reuse, or anything like that. People often get these problems confused because they’re similar, but in this specific example, I’m looking at best-case scenarios for query optimization – this query doesn’t even have parameters, and we’re getting a fresh execution plan each time, specifically designed for Lithuania. (You can try recompile hints to prove to yourself that plan reuse isn’t the issue here, and even with the filtered stats solution, you’d also still have parameter sniffing issues on reusable plans for different locations.)
So would I recommend this solution to solve the above problem? Probably not – but again, like I said, I have to teach you how to solve it in a simple scenario before I move into a more complex, real-world scenario. That one’s coming up in the next Query Exercise where we’ll add in larger tables and more query complexity.
If you want to see even more solutions, check out the comments on the challenge post. Thomas Franz had a particularly interesting one: he created 26 filtered stats, one for each letter of the alphabet, effectively giving him over 5,000 buckets for his statistics!
Hope you enjoyed the challenge! For more exercises, check out the prior Query Exercises posts and catch up on the ones you missed so far.
I ran a poll over on LinkedIn to find out if people have 100% of their databases under some kind of source control or version control.
I broke the answers up into two sets, developers and non-developers, because I had a hunch that the developers’ answers would be very different than the rest, and indeed they were:
Half (36% + 13% = 49%) of the audience says they have 100% of their database structure & logic under source control – and that’s fantastic for them! I love it. That’s actually better than I expected – however there’s a catch, and I’ll talk about that in a second.
Half (23% + 27% = 50%) says they don’t – and while I’d love for that to be different, I understand that it’s really, really hard. I wish databases like SQL Server just had a “put the source control here” option that would automatically check all changes into a repository, along with who made the change and when, so we had a history. The reality is that today’s databases have no built-in integration with source control, which means you’re gonna have to put in some kind of product and process to make that happen.
Half of the audience is better than none, right? Well, not so fast: remember how I split the audience into developers and non-developers? When I’ve discussed this issue with client teams, the developers have often believed everything was in source control, but the DBAs and sysadmins told a very different story. They’d say, “Uh, well, actually, there have been a lot of changes made to the environment post-deployment, especially by support engineers and vendors, and that stuff hasn’t quite made it back to version control.”
So in reality, even though a high percentage of developers believe they’re doing it right, they’re being let down by other people at the company. Hell, you can even be let down by Microsoft themselves! The ever-funny Sean Alexander pointed out:
Because Azure SQL DB can automatically add and remove indexes in your production database without leaving a bread crumb trail behind to undo its work. Don’t get me wrong, I think that’s a good thing for small to midsize businesses who can’t afford to have a DBA paying attention to every database – but it’s amusing that it works against the goals of source control.
If your databases are 100% in source control, great! I’m so happy for you, and you’re doing it right. You should be really proud of yourself and your team for putting in good practices. It’s now up to you to blog, present, and stream about how other companies can follow in your footsteps. There’s a severe lack of online content around how to do this well with SQL Server and the other Microsoft databases.
If not, take heart: you’re not alone. Even in 2024, lots of your peers are still struggling with the same challenge. To read more about their struggles, check out Mala’s recent roundup of SQL source control blog posts.
I took your top-voted questions from https://pollgab.com/room/brento, including a few career-oriented ones:
Here’s what we covered:
This Query Exercise was very different: I didn’t ask you to solve a particular problem. I pointed out that I’ve heard advice that SELECT MAX is faster than SELECT TOP 1, and that’s not quite true. I asked you to find factors that would cause these two queries to get different execution plans:
SELECT TOP 1 LastAccessDateFROM dbo.UsersORDER BY LastAccessDate DESC;SELECT MAX(LastAccessDate)FROM dbo.Users;
In the exercise post, I showed that with a nonclustered rowstore index like this, whose leading column is not LastAccessDate:
CREATE INDEX Location\_LastAccessDateON dbo.Users(Location, LastAccessDate);
That would give them different execution plans, leading to TOP 1 being faster because it went parallel, while MAX stayed single-threaded and ran longer.
I tell you what, when I was writing that blog post, the hardest thing by far was not to give away too many answers. I worded that sentence above really, really carefully because right there in that sentence alone, there’s a factor that changes the answers. We could just change something about the environment so that the MAX query goes multi-threaded too (or the TOP 1 query goes single-threaded!)
Cost Threshold for ParallelismContinuing with the above example, let’s change Cost Threshold for Parallelism from the common 50 down to, say, 25:
EXEC sys.sp\_configure N'cost threshold for parallelism', N'25'GORECONFIGUREGO
And then run our two queries again. Now, both of them go parallel, as shown in the actual execution plans:
Now both queries qualify for parallelism, and the MAX is faster. On the flip side, if we raise CTFP to a much higher number, like 500, the new execution plans are… wait… hang on a second here…
How does the TOP 1 query still have parallelism? Let’s hover our mouse over the SELECT and examine its Estimated Subtree Cost:
How’s that possible? If my Cost Threshold for Parallelism is 500, how can a query with a cost of 499.193 go parallel? Well, there’s a trick: we’re looking at the estimated cost of the parallel query, not the serial one. To see the cost of the serial query, add an OPTION (MAXDOP 1) hint to it:
SELECT TOP 1 LastAccessDateFROM dbo.UsersORDER BY LastAccessDate DESC OPTION (MAXDOP 1);
And we can see that the serial cost is a whopping 977 query bucks:
So that’s why the query goes multi-threaded. Parallelism: it’s a hell of a drug. Alright, let’s reset the playing field before we try other factors:
EXEC sys.sp\_configure N'cost threshold for parallelism', N'50'GORECONFIGUREGODropIndexes;GO
Columnstore IndexesThey’re great for aggregate queries like MAX, so let’s slap one on and see how it affects performance:
CREATE NONCLUSTERED COLUMNSTORE INDEX LastAccessDateON dbo.Users(LastAccessDate);GOSELECT TOP 1 LastAccessDateFROM dbo.UsersORDER BY LastAccessDate DESC;SELECT MAX(LastAccessDate)FROM dbo.Users;
The actual query plans look similar in the sense that they have the same operator, but the devil’s in the details on this one:
The TOP 1 uses a sort, which sounds bad, because it sounds like it would sort all 8,917,507 rows – especially with that monster arrow coming out of the columnstore index scan operator. However, that arrow doesn’t mean jack, as we explain in the Fundamentals of Columnstore training class.
The bottom line is that the TOP 1 uses 93ms of CPU time and runs in 205ms. The MAX uses 16ms of CPU time and runs in 97ms. The MAX wins both ways here, but it’s not a dramatic win – most folks aren’t going to complain too much about the difference between these two plans.
However, there’s a catch to this comparison: my database happens to be in SQL Server 2016 or newer compatibility mode for this one. Watch what happens when we introduce yet another variable into this experiment…
Compatibility LevelI’ve still got the columnstore index in place, but let’s drop back to 2014 compatibility level:
ALTER DATABASE [StackOverflow] SET COMPATIBILITY\_LEVEL = 120
And then check our new actual query plans:
Sometimes, smaller plans are better. However, not in this case: the wide TOP 1 plan finishes in just 351ms, but the seemingly-simple MAX plan takes a whopping 2.2 seconds to run!
This comparison is also a great reminder that the percentage query costs on plans are absolutely useless and meaningless! I catch people saying, “The bottom query looks better because it’s only 4% of the cost,” but that’s just garbage:
It drives me crazy that Microsoft even includes this junk in query plans in the year 2024. They’re doing a disservice with that number.
What We Learned in This ExerciseI’ve only covered a few variables in the equation that make TOP 1 and MAX perform differently. For more, check out the comments on the Query Exercise post.
Database servers have a butterfly effect: even the slightest change, seemingly unrelated to anything else, can affect query performance all over the place. I’m not saying you have to test everything before you change anything at all – the real world is just too busy and complicated to do that.
That butterfly effect should teach you 3 things:
The short story: SQL Server 2022 finally saw some growth this quarter! Two years after the release, 1 in 10 SQL Servers is finally running the latest version.
The long story: ever wonder how fast people are adopting new versions of SQL Server, or what’s “normal” out there for SQL Server adoption rates? Let’s find out in the summer 2024 version of our SQL ConstantCare® population report.
SQL Server is still the king of the hill with >2x more market share than any other version, but it did drop by one percentage point this month. Here’s how adoption is trending over time, with the most recent data at the right:
SQL Server 2019 still continues to grow while everything else shrinks, with the exception of 2022 treading water:
Now that 2014 is officially unsupported, it’s interesting to see that the market share of SQL Server 2022 is about the same as the share of unsupported versions. I bet out in the wild, the unsupported version share is even higher just because folks don’t care enough to monitor the oldest zombies. (Plus, SQL ConstantCare doesn’t support pre-2008 versions, which actually still exist – I just got a consulting request for a SQL Server 2005 box this month. And the answer was no.)
If you compare SQL Server 2022’s adoption curve to 2019’s, you’ll notice that SQL Server 2019’s adoption didn’t really take off until 2022 Q3-Q4 – when SQL Server 2022 was released. There might be a few different reasons for that:
No matter which one it is (or something else), I’ll be curious to see what happens to 2022’s adoption rate when Microsoft announces the feature list, CPU core & memory limitations, and pricing of the next version of SQL Server. Will that be the thing that finally makes SQL Server 2022 take off?
New SQL ConstantCare® Feature Coming: GamificationBecause we’ve got data for so many servers, consulting clients often ask me, “How big is our database relative to others? Are we using a similar amount of hardware? Are we out of the ordinary compared to our peers?”
Soon, we’re going to give monthly badges to SQL ConstantCare® customers (both free and paid) based on a bunch of rankings. Who has the most data? Who has the largest servers? Who has the longest uptime? And they won’t just be badges of pride – there are also badges of shame, like whose servers have the most problems, or who’s muted/ignored the most recommendations.
I’ll be blogging about the various badges over the coming weeks, explaining how the rankings are calculated, where the data comes from, and what the typical leaderboard looks like so that you get a rough idea of what it looks like at the top – and at the bottom.
If there’s a metric that you’d like to see rankings for, feel free to add it in the comments! We just might build a badge for it.
Over the last week, I’ve been working on putting together a Postgres version of the Stack Overflow database, just like the SQL Server one that I’ve distributed for almost a decade now.
I’ve worked with Microsoft SQL Server for so long that I just kinda took backups for granted. We run a backup command, and SQL Server:
All without stopping transactions or closing the data files. It’s pretty nifty, and it works really well. The good part is that it’s very efficient at backing up the entire database, and restoring the entire database to a single point in time.
The drawback is that it’s impossible to restore a single object from backup, by itself. Oh sure, we’ve complained about it for years, and it’s the #2 top voted feature request, but it doesn’t seem to be happening anytime soon. We’ve learned to work around that by restoring the entire database somewhere else, and then extracting just the data we need.
PostgreSQL Backups are Totally Different.One of the gotchas of Postgres is that there are a million different ways to accomplish any task. You could stop the database service and get file-level backups, but of course that’s a bad idea for production databases. You could install extensions like Barman to automate backups for you, and in many cases that’s a great idea for production databases. However, we’re going to focus on the built-in way that most shops start with.
When you back up a database with pg_dump, it actually generates a text file with statements like CREATE TABLE and INSERT that reconstruct the data from scratch.
I’ll give you a moment to re-read that sentence.
At first, you’re going to be horrified, but give it a second and open your mind.
Sure, there are drawbacks: if you work with databases over a few hundred megabytes in size, it probably horrifies you to think about a text file that large. No worries: you can tell pg_dump to compress (zip) the output into a custom file format as it goes. Another drawback is that there’s no such thing as combining transaction log backups with this – if you want to get point-in-time recovery, you’re going to need a better solution than pg_dump.
However, pg_dump has some pretty intriguing advantages – for starters, table-level restores. The pg_restore documentation page has all kinds of switches for just restoring one table, or only restoring the data (not the schema), or just restoring specific indexes, or more.
Speaking of indexes, get a load of this: because pg_dump is only backing up the data, indexes don’t bloat your backup files. You could have 50 indexes on a table, but that index data itself isn’t getting backed up – only the index definition, aka the CREATE INDEX statement! At restore time, pg_restore reads the backup file, runs insert commands to load the data, and then when it’s all there, runs the CREATE INDEX statements necessary to re-create your indexes. Your database can be partially online while the indexes are re-created. (Is this restore strategy going to be faster? Probably not, and I’m not going to test it, but it’s wild to know.)
But here’s the part that’s really going to blow your mind: since the dump file is just a list of commands, it’s technically possible to restore a Postgres database back to an earlier version. Take a plain-text backup (or convert the existing one to plain-text format), and then execute the commands, looking for any errors caused by newer engine features. Edit the backup file to remove the unavailable features in your older version, and then try again.
Which Approach Is Better?Microsoft’s approach focuses on backing up (and restoring) the exact data file contents at extremely high speed, shoving the data out without concern for its contents. Properly tuned, it’s fast as hell. I’ve had clients who regularly backed up and restored 1-5TB databases in just 5-20 minutes. That’s useful when you’ve got very short SLAs, but not shared storage.
Microsoft’s integration with the transaction log also means that the full backup is very extensible. You can integrate it with log backups, or use it to seed transaction log shipping, database mirroring, or Always On Availability Groups. There’s just one backup approach in SQL Server, but it has all kinds of flexibility that Microsoft has built up over the decades.
On the other hand, there are a bunch of different ways to back up Postgres databases. If you choose the pg_dump approach, it also lends itself to all kinds of creative use cases right out of the box. The more I played with it, the more amused I was at its capabilities. For example, backing up data from AWS Aurora Postgres and restoring it to my local Postgres instance was a no-brainer. The fact that one was a platform-as-a-service database in the cloud, and the other was an on-premises database, just simply didn’t matter – something Azure SQL DB just can’t pull off, even though Microsoft manages the whole code stack.
Microsoft has just one backup tool, and it works really well – as long as you don’t need to do something unusual, like restore a single table or downgrade versions. Postgres has lots of backup tools that have more flexibility and power overall – buuuut, it’s up to you to pick the right one and then configure it in a way that supports your RPO/RTO.
I don’t get it. I’ve given this feature one chance after another, and every time, it takes a smoke break rather than showing up for work.
The latest instance involved the recent Query Exercise where you were challenged to fix a computed column’s performance. In the comments, some folks noted that performance of the query was actually great on old compat levels, like SQL Server 2008, and that it only sucked on newer compat levels like 2016 and later.
That would be the perfect use case for Automatic Tuning, I thought to myself! I’ve said this so many times over the last five years, but I’m an endlessly hopeful optimist, as anyone who knows me well would never say, so I gave it another chance.
We’ll set up the same user-defined function and computed column described in that blog post:
USE StackOverflow;GOCREATE OR ALTER FUNCTION dbo.IsValidUrl (@Url NVARCHAR(MAX))RETURNS BITASBEGIN DECLARE @Result BIT = 0 -- Regex pattern for a valid URL -- This pattern covers: -- - Scheme (http, https, ftp) -- - Optional username:password -- - Domain name or IP address -- - Optional port -- - Optional path -- - Optional query string -- - Optional fragment IF @Url LIKE 'http://%' OR @Url LIKE 'https://%' OR @Url LIKE 'ftp://%' BEGIN IF @Url LIKE '%://[A-Za-z0-9.-]%.%' -- Check for domain/IP after scheme AND @Url NOT LIKE '% %' -- No spaces allowed in URL AND @Url LIKE '%.[A-Za-z]%' -- Ensure there's a period in domain/IP part AND @Url LIKE '%/%' -- Ensure there's at least one slash after the domain AND @Url LIKE '%[A-Za-z0-9/\_-]%' -- Ensure there's at least one valid character in the path BEGIN SET @Result = 1 END END RETURN @ResultENDGOALTER TABLE dbo.Users ADD IsValidUrlAS dbo.IsValidUrl(WebsiteUrl);GO
We’ll set up an index on the Reputation column, and a stored procedure that’ll benefit from using that index:
CREATE INDEX Reputation ON dbo.Users(Reputation);GOCREATE OR ALTER PROC dbo.GetTopUsers ASBEGINSELECT TOP 200 *FROM dbo.UsersWHERE IsValidUrl = 1ORDER BY Reputation DESC;ENDGO
We’ll put our database in old-school compat level, and turn on Query Store to start collecting data at a frantically quick pace:
ALTER DATABASE CURRENT SET COMPATIBILITY\_LEVEL = 110 /* 2012 */GOALTER DATABASE SCOPED CONFIGURATION SET LEGACY\_CARDINALITY\_ESTIMATION = OFF;GOALTER DATABASE CURRENT SET QUERY\_STORE = ONGOALTER DATABASE CURRENT SET QUERY\_STORE (OPERATION\_MODE = READ\_WRITE, DATA\_FLUSH\_INTERVAL\_SECONDS = 60, INTERVAL\_LENGTH\_MINUTES = 1)GOALTER DATABASE CURRENT SET QUERY\_STORE CLEAR;GO
We’ll run the query a few times, noting that it runs blazing fast, sub-second:
EXEC dbo.GetTopUsers;GO 5
And check Query Store’s Top Resource Consuming Queries report to show that the plan is getting captured:
Now, let’s “upgrade” our SQL Server to the latest and “greatest” compatibility level, and turn on Automatic Tuning so that it’ll “automatically” “fix” any query plans that have gotten worse:
ALTER DATABASE CURRENT SET COMPATIBILITY\_LEVEL = 160 /* 2022 */GOALTER DATABASE CURRENTSET AUTOMATIC\_TUNING ( FORCE\_LAST\_GOOD\_PLAN = ON ); GO
Now, when we go to run our query again, it takes >30 seconds to run, burning CPU the entire time – and zee Automatic Tuning, it does nothing. Query Store shows that there’s a new plan, and that the runtime is way, way worse:
But sys.dm_db_tuning_recommendations shows nothing, even though Query Store is on and so is “Automatic” “Tuning”:
To see it in inaction, here’s a live stream:
I don’t get it. I’ve given this feature so many chances, and it’s never kicked in for me at the right times. I’m guessing I’m missing some secret set of steps I need to take, but whatever it is, it’s beyond me. Maybe you can get it to work in this scenario, and share your magic? Now’s your chance to make me look like a fool.
Well, I mean, like even more of a fool.
Is your company hiring for a database position as of August 2024? Do you wanna work with the kinds of people who read this blog? Let’s set up some rapid networking here.
If your company is hiring, leave a comment. The rules:
If your comment isn’t relevant or smells fishy, I’ll delete it. If you have questions about why your comment got deleted, or how to maximize the effectiveness of your comment, contact me.
Each month, I publish a new post in the Who’s Hiring category here so y’all can get the latest opportunities.
What a comfy morning to sit down with a breakfast margarita and tackle your top-voted questions from https://pollgab.com/room/brento.
Here’s what we covered:
The EightKB conference is a free event that focuses on SQL Server internals.
You can get the session details and register for the August 8th event at eightkb.online. The session lineup is below, each link being a calendar invite just so you can block out your calendar:
To see the full session abstracts and register, head to eightkb.online.
Every year, Stack Overflow runs a developer survey about technology, work, community, and more. This year’s results include 65,437 responses from developers around the world.
The results are biased towards the kinds of developers who use Stack Overflow – 76% of the respondents reported that they have a Stack Overflow account. I would guess that it’s nowhere near a perfect picture of all developers worldwide, but let’s just focus on the fact that it does represent how over 65,000 developers feel – and that alone is useful enough to give you a picture of what’s happening in at least a lot of shops worldwide.
The most-used databases were PostgreSQL, MySQL, SQLite, and Microsoft SQL Server, in that order:
When Jeff Atwood and Joel Spolsky first started Stack Overflow, Jeff did a lot of evangelization work for it, and Jeff’s audience was heavily biased towards .NET development. I would imagine that’s part of why SQL Server is by far the highest paid database in the list (as opposed to open source.)
I have a total blind spot around SQLite, but often on my TikTok videos when the discussion of licensing costs comes up, some commenters will ask why everyone in the world doesn’t use SQLite. I’m sure there are a lot of apps worldwide that simply don’t need a database server, only a small local relational storage, and I can understand why those developers would have a similar blind spot about what it’s like to handle concurrent load in an enterprise-wide ERP app or e-commerce store.
The next chart, admired-vs-desired, doesn’t make sense to me and I don’t trust the numbers:
From what I can tell, the blue scores indicate how much the respondents have worked with the database in the last year, and red scores indicate how much they want to work with it next year? I’m pretty confused by this one. Are the red scores exclusive to only the people who actually worked with the technology this year – meaning, out of the 15.4% of the audience that worked with SQL Server in the past year, 54.5% of them want to work with it next year?
And why don’t these numbers come anywhere near agreeing with the prior question? The prior question says 25.3% of the audience used it last year, but the admired-vs-desired question says only 15.4% did? I’m so lost. At first I thought this question is taking about “extensive” development work, whereas the first one might just be ANY database work – but the numbers don’t make sense there either, because Supabase scored 4% on the first question (any work), but 5.9% on this question (extensive work.) Both questions use the term “extensive.” I’m lost.
So yeah, I just discarded that question and didn’t bother to think about the results. It doesn’t make sense, so I don’t trust it.
There was also a question about which cloud provider folks used, and AWS dominated the market:
That’s been my experience too – the vast, vast majority of my cloud clients are on AWS – but I’m mentioning it here because I know that Azure users really seem to believe Azure’s the only game in town. When I talk to Microsoft MVPs, they seem dumbfounded that companies are actually using AWS extensively, and they also seem surprised that Microsoft is competing with Google for second place. (Google’s been throwing a lot of discounted/free compute power at prospective clients to win them over.)
There’s a lot more stuff in the overall results, especially the workplace trends section that talks about employment status, hybrid/remote/in-office, and salary. When you’re looking at each workplace graph, make sure to click on the geographic filter at the top of that graph so the numbers will be more meaningful to you, based on where you’re located.
While taking a dip in Cabo, I went through your top-voted questions from https://pollgab.com/room/brento.
https://www.youtube.com/watch?v=OaHImrIMM6Y
Here’s what we covered:
In last week’s Query Exercise, we added a user-defined function to the Users table to check whether their WebsiteUrl was valid or not. I noted that even with an index on Reputation, SQL Server 2022 simply ignored the index, did a table scan, and spent 2 minutes of time calling the user-defined function on a row-by-row basis.
First off, a disclaimer: I didn’t write that exercise with a goal of showing the difference between old & new SQL Server versions, between old & new compatibility levels. However, several folks in the comments said, “Hey, I’m on an older version, on an older compatibility level, and the query is blazing fast already. What do I need to fix?” That’s a good reminder of what work you need to do before you go live on SQL Server 2022 (or any new version.) Moving on.
One Fix: Persisting the Computed ColumnOne way to fix it is to drop the computed column, then create it again with the PERSISTED keyword:
ALTER TABLE dbo.Users DROP COLUMN IsValidUrl;GOALTER TABLE dbo.Users ADD IsValidUrlAS dbo.IsValidUrl(WebsiteUrl) PERSISTED;GO
That way, SQL Server computes each row’s contents just once, and then persists it in the table itself, instead of running the function every time we query it. By executing the above code, we get:
Wait, what? As a reminder, here’s the contents of our function:
CREATE OR ALTER FUNCTION dbo.IsValidUrl (@Url NVARCHAR(MAX))RETURNS BITASBEGIN DECLARE @Result BIT = 0 -- Regex pattern for a valid URL -- This pattern covers: -- - Scheme (http, https, ftp) -- - Optional username:password -- - Domain name or IP address -- - Optional port -- - Optional path -- - Optional query string -- - Optional fragment IF @Url LIKE 'http://%' OR @Url LIKE 'https://%' OR @Url LIKE 'ftp://%' BEGIN IF @Url LIKE '%://[A-Za-z0-9.-]%.%' -- Check for domain/IP after scheme AND @Url NOT LIKE '% %' -- No spaces allowed in URL AND @Url LIKE '%.[A-Za-z]%' -- Ensure there's a period in domain/IP part AND @Url LIKE '%/%' -- Ensure there's at least one slash after the domain AND @Url LIKE '%[A-Za-z0-9/\_-]%' -- Ensure there's at least one valid character in the path BEGIN SET @Result = 1 END END RETURN @ResultENDGO
HOW THE CELKO IS THAT NOT DETERMINISTIC? What, does string comparison change from time to time? Books Online’s article on determinism says “All of the string built-in functions are deterministic,” but I’m guessing this has something to do with a possibly changing collation, and I couldn’t care less about digging further. That’s left as an exercise for the reader. Moving on.
An Actual Quick Fix: WITH SCHEMABINDINGI used to hear people say, “You should put WITH SCHEMABINDING on your scalar functions and they’ll go faster.” Every single time I tried it, it made no difference whatsoever.
Until…last week! In the comments, Ag suggested to add that hint to the function, and it ran rapidly! We’ll remove the existing column, tweak the function – the only change is WITH SCHEMABINDING – and then add it back in:
ALTER TABLE dbo.Users DROP COLUMN IsValidUrl;GOCREATE OR ALTER FUNCTION dbo.IsValidUrl (@Url NVARCHAR(MAX))RETURNS BIT WITH SCHEMABINDINGASBEGIN DECLARE @Result BIT = 0 -- Regex pattern for a valid URL -- This pattern covers: -- - Scheme (http, https, ftp) -- - Optional username:password -- - Domain name or IP address -- - Optional port -- - Optional path -- - Optional query string -- - Optional fragment IF @Url LIKE 'http://%' OR @Url LIKE 'https://%' OR @Url LIKE 'ftp://%' BEGIN IF @Url LIKE '%://[A-Za-z0-9.-]%.%' -- Check for domain/IP after scheme AND @Url NOT LIKE '% %' -- No spaces allowed in URL AND @Url LIKE '%.[A-Za-z]%' -- Ensure there's a period in domain/IP part AND @Url LIKE '%/%' -- Ensure there's at least one slash after the domain AND @Url LIKE '%[A-Za-z0-9/\_-]%' -- Ensure there's at least one valid character in the path BEGIN SET @Result = 1 END END RETURN @ResultENDGOALTER TABLE dbo.Users ADD IsValidUrlAS dbo.IsValidUrl(WebsiteUrl);
Then rerun our query without changes:
SELECT TOP 200 *FROM dbo.UsersWHERE IsValidUrl = 1ORDER BY Reputation DESC;
Even on a large Stack Overflow database, the query runs in milliseconds and produces a great execution plan:
SQL Server uses the index only only reads a few hundred rows before the work is done. Sure, there’s no parallelism in the query, and the plan properties report that “TSQLUserDefinedFunctionsNotParallelizable”, but who cares? The query’s so fast, even with millions of candidate rows, that it doesn’t matter. Good enough, ship it.
My Old Faithful Fix: A TriggerI know. I know, the word makes you break out in itchy hives, but triggers are a great way to quickly replace computed columns.
Let’s drop the existing computed column, add a new “real” column, configure the trigger to populate it going forward, and then backfill it to set up the existing data:
ALTER TABLE dbo.Users DROP COLUMN IsValidUrl;GOALTER TABLE dbo.Users ADD IsValidUrl BIT;GOCREATE OR ALTER TRIGGER Users\_IsValidUrl ON dbo.UsersAFTER INSERT, UPDATEAS BEGINUPDATE uSET IsValidUrl = dbo.IsValidUrl(u.WebsiteUrl)FROM dbo.Users uINNER JOIN inserted i ON u.Id = i.Id;END; GO UPDATE dbo.UsersSET WebsiteUrl = WebsiteUrl;GO
The first few statements will run instantly (assuming nobody else is locking the Users table), but the last statement will take a minute since it’s actually adding a new value to every row in the Users table, AND it’s calling a scalar function on every row. You could make that faster with set-based operations, but more on that in a minute.
After that, run the query we’ve been trying to tune:
SELECT TOP 200 *FROM dbo.UsersWHERE IsValidUrl = 1ORDER BY Reputation DESC;
It runs in a couple milliseconds (down from a minute) and uses the Reputation index, as the execution plan shows:
I like the trigger solution for a few reasons:
But there’s a drawback: the trigger’s still getting called on every insert and update. If the workload involves batch jobs to load or modify the table, that’s going to be problematic because they’re going to slow down. Those modifications didn’t call the computed column’s function before since they most likely weren’t reading that column. If our workload includes batch jobs, we’re gonna need a faster solution. We’ll clean up after ourselves before trying the next one:
DROP TRIGGER [dbo].[Users\_IsValidUrl]GOALTER TABLE dbo.Users DROP COLUMN IsValidUrl;GO
Rewriting the FunctionMost of the time when you hear database people complaining about functions, they’re complaining about user-defined functions. For example, in this case, we can get a dramatic performance improvement if we forklift the business logic out of the user-defined function and inline it directly into the table’s definition itself. Here’s the end result:
ALTER TABLE dbo.Users ADD IsValidUrlAS CASE WHEN (WebsiteUrl LIKE 'http://%' OR WebsiteUrl LIKE 'https://%' OR WebsiteUrl LIKE 'ftp://%')AND (WebsiteUrl LIKE '%://[A-Za-z0-9.-]%.%' -- Check for domain/IP after scheme AND WebsiteUrl NOT LIKE '% %' -- No spaces allowed in URL AND WebsiteUrl LIKE '%.[A-Za-z]%' -- Ensure there's a period in domain/IP part AND WebsiteUrl LIKE '%/%' -- Ensure there's at least one slash after the domain AND WebsiteUrl LIKE '%[A-Za-z0-9/\_-]%' -- Ensure there's at least one valid character in the path ) THEN 1 ELSE 0 END;GO
Note that the logic isn’t identical because I had to tweak it: instead of using IF, I used CASE statements, and there’s no @Url parameter anymore because we’re referencing the WebsiteUrl column directly. The more complex your user-defined function is, the harder work this is going to be. To learn how to do these kinds of rewrites, check out Microsoft’s PDF whitepaper on Froid, the inline function technology they added in SQL Server 2019. That paper gives several specific examples of language to use when rewriting functions to go inline, which is quite nice of them.
Now, when the SELECT runs, the plan looks big:
And while the estimates aren’t good, the plan shape is fine, it uses the index, and it runs in milliseconds.
Eagle-eyed readers will note that I did not use the PERSISTED keyword in the above example. If you add that, the resulting plan shape looks even better because the function doesn’t have to run at read time:
And the query finishes in even fewer milliseconds. However, executing the ALTER TABLE with the PERSISTED keyword will lock & rewrite the table, so it’s a question of whether your workload can tolerate that outage. You could always start with the non-persisted version to quickly make the pain go away, and then later if you still need more performance, take an outage to drop the non-persisted computed column (which will be instantaneous) and then add the persisted one (which will be the opposite of instantaneous.)
Hope you enjoyed the challenge! For more exercises, check out the prior Query Exercises posts and catch up on the ones you missed so far.
I give away a lot of stuff in various places on the internet: videos, scripts, the new free tier of SQL ConstantCare®, and I’ve got more tricks coming soon.
But until now, it’s been kind of a hassle because they’ve been scattered all over the place, not in a sensible order to help you onboard gradually.
So now, my shop page has a simplified set of bundles, starting with an absolutely free membership:
Free$0per year* SQL ConstantCare® with Health Monitoring * Introductory Classes: * How to Think Like the Engine * How I Use the First Responder Kit
Sign upFundamentals$995per year* SQL ConstantCare® with Health & Performance Monitoring * Fundamentals Classes * Purchased Individually: $1,542
Sign upMastering$1,995per year* Everything in * Fundamentals, plus: * Mastering Classes * The Consultant Toolkit * Purchased Individually: $3,617
Sign upThis new Free Stuff bundle will make it easier for me to put my free resources all in one place, in a more logical order. Folks can track their progress through the resources, checking chapters off as they work through ’em.
I updated my free How to Think Like the Engine class this week with a new recording using the 50GB Stack Overflow 2013 database. This way, people can get started on the exact same database that I use throughout the Fundamentals classes, making it a little easier to get onboard.
I’ve also made my How I Use the First Responder Kit class completely free. I’ve always believed that if I can solve your problems for free, then I wanna be the person who does that, so I might as well put you in the best position possible to use the FRK scripts.
For the pros, I’ve also added a new Mentoring tier.The Mentoring tier includes everything in Mastering, plus:
That tier is $3,495/year for 1 person. It’s designed for folks who don’t quite need my full 2-day hands-on SQL Critical Care® engagement, and just want periodic feedback from time to time.
Frequently Asked Questions“If I have an existing subscription, does this change anything?” Nope! Not at all. You can continue with your current one, or if you’d like to change something, you can cancel your existing subscription and pick up one of these instead. (We don’t have the ability to upgrade in place.)
“Can I still buy classes individually?” Yep! Just keep scrolling down on the shop page. The vast, vast majority of my buyers pick up the bundles so I keep those at the top.
“Can I buy the mentoring without training?” No, because often my advice includes a link to a specific training module to explain the issue you’re facing.
“Can I get group discounts?” Yes, if you’re enrolling 5 or more people in the same membership tier, email us at Help@BrentOzar.com with the tier you want, and the list of email addresses to register, and we’ll get you a quote.
For other questions, feel free to drop ’em in the comments or contact me.
Last night, two major IT disasters struck:
If you were affected by one of those outages, you have my warmest virtual hug. At times like this, the stress level can be really tough, and I hope you can take care of yourself. Remember that your own self-worth is not determined by the IT solutions you work on.
If you weren’t affected by one of those outages, it’s a good time to spend an hour writing up a few things:
Summarize that, pass it up to your manager in writing, and it’ll help them have discussions this morning with their managers and executives. Today, a lot of business folks are going to be asking questions, and having these answers will help get you the resources you want.
(Or, it’ll help you feel more comfortable that the business understands the risks of putting all their eggs in a single basket, and that when that basket breaks, it’s not your fault. You warned ’em, and they chose not to spend the money to double-up on baskets.)
On a pleasantly mild morning, I sat down on the patio and took your top-voted questions from https://pollgab.com/room/brento.
Here’s what we covered:
Take any size of the Stack Overflow database and check out the WebsiteUrl column of the Users table:
Sometimes it’s null, sometimes it’s an empty string, sometimes it’s populated but the URL isn’t valid.
Let’s say that along the way, someone decided to ask ChatGPT to build a function to check for valid website URLs, and then used that code to add a new IsValidUrl column to the Users table (and yes, this is inspired by a real-life client example, hahaha):
CREATE OR ALTER FUNCTION dbo.IsValidUrl (@Url NVARCHAR(MAX))RETURNS BITASBEGIN DECLARE @Result BIT = 0 -- Regex pattern for a valid URL -- This pattern covers: -- - Scheme (http, https, ftp) -- - Optional username:password -- - Domain name or IP address -- - Optional port -- - Optional path -- - Optional query string -- - Optional fragment IF @Url LIKE 'http://%' OR @Url LIKE 'https://%' OR @Url LIKE 'ftp://%' BEGIN IF @Url LIKE '%://[A-Za-z0-9.-]%.%' -- Check for domain/IP after scheme AND @Url NOT LIKE '% %' -- No spaces allowed in URL AND @Url LIKE '%.[A-Za-z]%' -- Ensure there's a period in domain/IP part AND @Url LIKE '%/%' -- Ensure there's at least one slash after the domain AND @Url LIKE '%[A-Za-z0-9/\_-]%' -- Ensure there's at least one valid character in the path BEGIN SET @Result = 1 END END RETURN @ResultENDGOALTER TABLE dbo.Users ADD IsValidUrlAS dbo.IsValidUrl(WebsiteUrl);
The user-defined function isn’t accurate, for starters – it’s letting things through that aren’t valid URLs, and stopping things that are actually valid – but let’s set that aside for a second.
What happens when we try to get the top users by reputation? To give SQL Server the best shot, I’m using SQL Server 2022, with the database in 2022 compatibility level, with an index on Reputation:
CREATE INDEX Reputation ON dbo.Users(Reputation);SET STATISTICS TIME, IO ON;GOSELECT TOP 200 *FROM dbo.UsersWHERE IsValidUrl = 1ORDER BY Reputation DESC;
The actual query plan is deceivingly simple, despite its terrible performance that takes about a minute to run:
Crouching Tiger, Hidden ScalarWHERE IS YOUR FUNCTION INLINING GOD NOW? I could make movie jokes about this all day. Anyhoo, the plan ignored the Reputation index, did a 2-second table scan, and spent nearly a minute doing the scalar function and the filtering.
To add insult to injury, if you’re going to do 1 minute of CPU work, it sure would help to parallelize that query across multiple cores – but that query can’t get parallelism, as explained in the plan properties:
SpacesNotAvailableEitherYour Query Exercise this week isn’t to fix the accuracy of the function – you can leave it as inaccurate if you like. Your challenge is to have the exact same query run in less than a second. Our goal is to avoid changing application code, and to get a very fast fix in place without blaming the developers. You’re the data professional: be professional.
Put your queries in a Github Gist, and include that link in your comments. Check out the solutions from other folks, and compare and contrast your work. I’ll check back next week with my thoughts. Have fun!
Update: please read the post in its entirety, and follow the instructions. Please don’t just throw ideas in there or half-formed T-SQL. For someone to test your work, they need to see your exact work. C’mon, folks – this isn’t a major project, just a single function. Be fair to people on the other side of the screen.
Stack Overflow publishes a data dump with all user-contributed content, and it’s a fun set of data to use for demos. I took the 2024-April data dump, and imported it into a Microsoft SQL Server database.
It’s an 31GB torrent (magnet) that expands to a ~202GB database. I used Microsoft SQL Server 2016, so you can attach this to anything 2016 or newer. If that’s too big, no worries – for smaller versions and past versions, check out my How to Download the Stack Overflow Database page.
Some quick facts about this latest version:
As with the source data, this database is licensed under cc-by-sa-4.0: https://creativecommons.org/licenses/by-sa/4.0/ And to be very clear, this is not my data. The data and the below licensing explanation comes from the Stack Overflow Data Dump’s page:
But our cc-by-sa 4.0 licensing, while intentionally permissive, does require attribution:
Attribution — You must attribute the work in the manner specified by the author or licensor (but not in any way that suggests that they endorse you or your use of the work). Specifically the attribution requirements are as follows:
This will probably be the last database update.Prosus (a tech investment company) acquired Stack Overflow a few years ago for $1.8 billion. When a company’s founders sell their baby for money:
So now Prosus wants to earn their $1,800,000,000 back, and they’re looking at the actual product they bought. StackOverflow.com has 3 components[1]:
Can Prosus compete on #1? No. Just no. Companies like OpenAI (ChatGPT), Google (Gemini), and Anthropic (Claude) simply have a better solution for #1, full stop, end of story. A web site – even a free one – can’t beat ChatGPT’s ability to integrate directly with your development environment, review your code & database, and recommend specific answers for the problem you’re facing. Game over.
Can Prosus compete on #2? No. The existing answers (as of April 2, 2024) are available for free with nearly no restrictions. The horse is already out of the barn. Moving on.
Can Prosus compete on #3? If ChatGPT and their friends win on #1 and #2, then the default place for developers to find answers is no longer the web browser. (It’s ChatGPT or Copilot or whatever). Whatever happens next is going to be intriguing. Today, you and I are conditioned to think, “I’ll post that question on Stack or a forum.” Tomorrow’s developers will not have that same bias:
I don’t see an easy way for Stack Overflow to inject themselves into that workflow in the year 2030. I’m sad about that because I have a long personal history with Stack Overflow. At the same time, I’m also kinda glad that the original founders, employees, and advisors (me included) were able to cash out thanks to Prosus’s $1.8B overspending just before the generative AI boom hit.
Prosus needs solutions fast: Stack is now losing $150,000 per day. Prosus’s 2024 annual reports noted that Stack Overflow had $98M in incoming revenue – but lost $57M. I can understand why managers might flail at a company’s switches and dials trying to find a way to stop the financial bleeding.
One of the dials they’ve been flailing at is turning down community access to the past answer data, aka business part #2. In their minds, they’re trying to stop OpenAI/Google/Anthropic from making so much money on the back of Stack’s answers. Earlier this year, Prosus tried to pump the brakes on providing the data dumps in XML format on a regular basis, and there was some community outrage, so they relented. However, they’re back: last week, Prosus announced they’re limiting access again.
Based on what Prosus is saying in that post, going forward, I don’t think Prosus will approve of me redistributing new data dumps in a database format. I’m not going to waste time or energy fighting that battle – I’d rather they spent their own energy trying to figure out a way to keep StackOverflow.com a viable business concern going forward. Hopefully they find fun, productive ways to do that, ways that bring the community together onto Prosus’s side rather than turning consumers against Prosus.
However, if Prosus management is willing to limit the data dump, then I have a bad feeling that more barriers are coming over the years. Next, they’ll make answers harder to access for people who have an ad blocker, or who aren’t signed in, or who haven’t paid for a “premium” Stack membership. I’m not mad at them about this, because I don’t have any answers to turn the business around either, and I haven’t heard from anybody who does.
You either die a hero or live long enough to see yourself become the arch-enemy.
[1] Technically the company Stack Overflow has a couple other parts: advertising and Stack Overflow for Teams. Both of those business models are at risk due to AI as well. Their other attempts at diversification, like Articles and Jobs and Developer Story, never caught on.
I went through your top-voted questions from https://pollgab.com/room/brento while in front of the National Gallery for Foreign Art in Sofia.
Here’s what we covered:
You already use sp_Blitz and the rest of the free, open source First Responder Kit to give your SQL Servers a health check now and then.
But let’s be honest: you don’t do it often enough.
You wish you had an easier way to know when your backups stop working, when corruption strikes, when a poison wait is detected, or when a new SQL Server patch comes out for one of your servers.
Good news! We’ve now got a free version of SQL ConstantCare®!SQL ConstantCare® is our simple monitoring product that you install on a jump box or VM. It connects to your SQL Servers, Azure SQL DB, Amazon RDS, etc. just once per day, gathers diagnostic data, and sends it to our processing servers in AWS. We generate a single daily email per server telling you specific, actionable tasks that will make your databases safer.
The full-blown $695/year version of SQL ConstantCare® gives you performance advice on stuff like query plans, indexes, memory settings, and wait stats, but there are a lot of folks out there who just don’t need that. They can’t fix the databases, queries, or indexes, and they just wanna know that the server is healthy.
That’s where our free health-only monitoring comes in. Sign up here for free, then download the installer, and follow the installation instructions. In a matter of minutes, you’ll get an email, and then again once a day – but only when we’ve got stuff you actually need to do on the server. Otherwise, we leave you alone to do the other important stuff you gotta do every day.
Why are we doing this? What’s the catch?For now, it’s for the first 100 free-tier users. I want to be careful as we scale this in a way that I can afford it, and Richie & I can support it. We’re pretty confident we can do it at a much larger scale, but we’re starting small.
It’s easy for us to support because we’re not doing anything complex like putting agents on SQL Servers, or running 24/7 monitoring. The app just runs on your jump box as a scheduled task, once per day, and that’s it.
It’s cheap for us to provide because the health-only processing costs us less than $1 per monitored SQL Server per month. We designed SQL ConstantCare® to be serverless and cloud-native right from the start, and Richie’s done a lot of work keeping the code and database lean and mean, so scaling has been pretty easy.
Our back end is fully cloud-hosted, which means your diagnostic data goes up to our services in AWS. If you’ve got questions about how that works, check out how we collect, transmit, and store your data and the frequently asked questions. To help us with GDPR compliance, we automatically delete all data older than 30 days. We won’t ever offer a version of SQL ConstantCare® that you can host yourself – we rely on too many AWS services, and helping you set all that up and troubleshoot it would make the price tag crazy high. If you want your own private monitoring, you’re better off buying a conventional 24/7 monitoring app. Of course, those are more expensive, and priced per monitored server – but if you want that level of control, that’s the price you pay. (Literally.) We’re trying to help as many people as we can here, as inexpensively as possible.
The signup process is a little wonky, not as smooth as I’d like. When you click the signup link, you’ll be asked to sign up with an email or Google. If you’ve bought training classes or software from me in the last few years from training.brentozar.com, you can use the same login. If you hit any roadblocks signing up and you can’t figure out how to get past ’em, email Help@BrentOzar.com and include a screenshot of the full browser, including the URL you’re on.
I hope this helps make your job easier. Like the First Responder Kit, this here blog, and the tons of videos we put out across my YouTube channel, TikTok, LinkedIn, etc, I really want to help do as much as I can, for free. That way, when you need training or consulting, you’ll remember who loves ya, baby. Enjoy!
For T-SQL Tuesday this month, Louis Davidson suggested we give our past self some advice.
I’d tell myself, “Use ‘we’, not ‘you’.”
For years, when I gave advice, I’d say things like:
The very word ‘you’ sets up a confrontational tone that puts the recipient on the defensive. They can’t help but react by taking things personally. We’re just humans, meatbags of emotion.
Instead, use words like ‘we’ and ‘our’ that group us together. We’re on the same team, and our common enemy is technology. Dagnabbit, technology sucks hard. It’s always out to get us, to make our lives miserable, to refuse to work the way it says it’ll work in the manual.
Once we (see what I did there) get started using the term ‘you’ early on in our careers, it’s a really hard habit to break. I know that, because I’ve been trying to break it for years.
I do use the term ‘you’ a lot in blog posts and videos that are purposely designed to be confrontational and drive engagement. That’s on purpose. However, when I wanna give advice to someone on my own team, I try to remember that we are indeed on the same team, and I need to communicate that by using the word ‘we.’
I was honored to speak at the Present to Succeed conference in Sofia, Bulgaria, run by a former SQL Server MCM. Sofia is a beautiful city, and the gorgeous Patriarchal Cathedral of St. Alexander Nevsky (Wikipedia) was close to my hotel, so I dragged my tripod over there for an Office Hours session.
Here’s what we covered:
Next Tuesday, pricing on the 3-day tickets for the PASS Data Community Summit goes up to $2,095.
But if you register right now, plus use coupon code BRENTO24, it’s just $1,745.
It’s one in-person conference that covers Microsoft SQL Server, Azure, PostgreSQL, Snowflake, Oracle, and more. So many of us (me included!) are working with multiple databases these days, and it’s hard to find a single event with this kind of coverage.
I’m also teaching a 1-day pre-conference workshop on Monday, Tuning T-SQL for SQL Server 2019 and 2022. That’s an additional $595, and you can also sign up for another pre-conference workshop on Tuesday. There are great options in there including Query Quest with Erik & Kendra, Microsoft Fabric in a Day, PostgreSQL Fundamentals, Power BI Architecture, and a SQL AI Workshop run by Microsoft folks.
Go register now, and I’ll see you in Seattle!
At the PGConf.dev, where Postgres developers get together and strategize the work they wanna do for the next version, I attended a session where Matthias van de Meent talked about changing the way Postgres stores columns. As of right now (Postgres 17), columns are aligned in 8-bit intervals, so if you create a table with alternating columns:
Matthias pointed out that was inefficient, and that Postgres should separate physical column order from logical column order. Under the hood, it should just store:
Microsoft SQL Server already does this with bits, as the documentation explains:
The SQL Server Database Engine optimizes storage of bit columns. If there are 8 or fewer bit columns in a table, the columns are stored as 1 byte. If there are from 9 up to 16 bit columns, the columns are stored as 2 bytes, and so on.
To demonstrate that, I whipped up a demo script showing two table creations: one with the bit columns scattered around through the table, and one where they’re all grouped together:
CREATE DATABASE TestColumnAlignment;GOUSE TestColumnAlignment;GOCREATE TABLE dbo.Disorganized(Id INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,TinyInt1 TINYINT,Bit1 BIT,TinyInt2 TINYINT,Bit2 BIT,TinyInt3 TINYINT,Bit3 BIT,TinyInt4 TINYINT,Bit4 BIT,TinyInt5 TINYINT,Bit5 BIT);CREATE TABLE dbo.Organized(Id INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,Bit1 BIT,Bit2 BIT,Bit3 BIT,Bit4 BIT,Bit5 BIT,TinyInt1 TINYINT,TinyInt2 TINYINT,TinyInt3 TINYINT,TinyInt4 TINYINT,TinyInt5 TINYINT);INSERT INTO dbo.Disorganized(Bit1, Bit2, Bit3, Bit4, Bit5, TinyInt1, TinyInt2, TinyInt3, TinyInt4, TinyInt5)SELECT 1, 1, 1, 1, 1, 1, 1, 1, 1, 1FROM GENERATE\_SERIES(1, 10000000);INSERT INTO dbo.Organized(Bit1, Bit2, Bit3, Bit4, Bit5, TinyInt1, TinyInt2, TinyInt3, TinyInt4, TinyInt5)SELECT 1, 1, 1, 1, 1, 1, 1, 1, 1, 1FROM GENERATE\_SERIES(1, 10000000);GOEXEC sp\_BlitzIndex @Mode = 2;
sp_BlitzIndex shows that both tables have the same size:
If we add another bit column to both tables:
ALTER TABLE dbo.DisorganizedADD Bit6 BIT;ALTER TABLE dbo.OrganizedADD Bit6 BIT;GOEXEC sp\_BlitzIndex @Mode = 2;GO
Size still remains the same:
Because SQL Server’s just making a metadata-only change, noting that 1 of the 8 bits in the bit-designated space is now available for use by the new Bit6 column. To really drive that point home, let’s go back and update the new Bit6 column to be 1:
UPDATE dbo.Disorganized SET Bit6 = 1;UPDATE dbo.Organized SET Bit6 = 1;GOEXEC sp\_BlitzIndex @Mode = 2;GO
And then check the space used again:
Yep, still 194MB. Good work, Microsoft.
The more I learn about Postgres, the more I appreciate so many little things that Microsoft has done over the years for performance & space optimization. The one that’ll really surprise you is that Postgres still doesn’t have table or index compression yet, although it does offer value-level compression.
I’m not talking just about Microsoft SQL Server specifically here, nor T-SQL. Let’s zoom out a little and think bigger picture for a second: is the SQL language itself a problem?
Sometimes when I talk to client developers, they gripe about the antiquated language.
The order of a SELECT statement doesn’t make any sense. You shouldn’t state what you’re looking for, before you even say where you wanna get the data from. The FROM should really go first so that query-completion tools like IntelliSense have a fighting chance to help you write the SELECT part. If we started writing our queries like this:
FROM dbo.Users uINNER JOIN dbo.Posts p ON u.Id = p.OwnerUserIdSELECT ...
Then as you started typing stuff in the SELECT, you could actually get useful stuff out of IntelliSense. How many times have you started typing a query, and query completion tools start throwing all kinds of system functions at you? Idiotic.
Exception handling is a painful mess. Let’s be honest here: the majority of stored procedures and functions out there don’t have error handling. They YOLO their way through the data, hoping and praying that things are as we expect, we have the right permissions, structures haven’t changed, and the data is in a useful state. Everybody looks the other way and mumbles, “We’ll handle errors on the application side,” when in reality those errors are either thrown directly at the innocent user, or simply suppressed and not logged anywhere.
It’s not really a standard. Oh sure, SELECT/FROM/WHERE/ORDER BY works in most databases, but even trivially simple applications break if you try to port them from one database management system to another. Your skills transfer in a similar way: even if you’re great at T-SQL exception handling, you’re still gonna have to tweak the way you do it in Postgres. The concepts are standard, but the specifics are different.
Unit testing is a pipe dream. App code developers know if their code changes will break something. Database developers just punt their stuff into development, run the query a few times, nod because no errors get thrown, and then toss it into production. When code breaks weeks or months later, all we hear is, “Nothing’s been changed.”
So why haven’t we moved on past SQL?In some ways, we have, with object-relational mapping (ORM) tools like Entity Framework, Hibernate, and Django. The database administrator readers here in the audience usually cringe when they hear those words, but the reality is that developers leverage those tools heavily to build new applications. I don’t blame them. I would too, for all the reasons I talked about above.
What those tools do is translate your desires into SQL, though, which brings us right back where we started. Often, the SQL they generate sucks for performance, thus the typical DBA’s feelings about ORMs. So why haven’t we got a new standard way for applications to talk directly to databases, in a secure, performant, and easy-to-write way?
It’s not for lack of trying: at least once every 6 months, I see a post on HackerNews about a better replacement for SQL. Someone puts a lot of thought into the problems, puts a lot of work into a replacement, and then proudly announces it.
And nobody uses it.
Because SQL is the lowest common denominator that works damn near everywhere, for values of “works.”
It works on the back end. Remember when NoSQL came out, and everybody was all “databases r doomd”? And remember what business users said when they wanted to run their reports? NoSQL persistence layers pretty quickly changed their tune, saying, “Oh, well, uh, we meant Not Only SQL, that’s what we meant,” as they struggled to quickly slap in SQL compatibility. Even MongoDB, king of NoSQL, implemented SQL support.
It works on the front end, especially the reporting front end, which is what managers care about. The people who sign the checks wanna see their data in Power BI and Excel. Every new reporting tool that comes out, in order to check boxes and say they’re compatible with every database, implements SQL support. Oh sure, these tools write horrific queries, but they check the box to say they can get data out of all your different persistence layers, and they do it with SQL first because it’s cheap and easy to support lots of databases that way.
I’ll leave you with an amusing quote from Bjarne Stroustrup:
There are only two kinds of languages: the ones people complain about and the ones nobody uses.
I went through your top-voted questions from https://pollgab.com/room/brento before heading out to PGConf.dev in Vancouver.
Here’s what we covered:
For this week’s Query Exercise, I asked you to write a better query than ChatGPT wrote. Your goal was to find the best days and times to post questions on Stack Overflow.
I found it interesting that a lot of the initial answers focused on the times when there were the most questions, or which questions were the most highly upvoted. For me, the best time to post a question is when you have the highest likelihood of getting the right answer, quickly.
When someone posts a question, they can accept an answer as the right one. You can see it by looking for checkmarks next to an answer. The checkmark indicates that the answer was accepted by the original question-asker.
The accepted answer may not be the best one overall, especially as additional answers come in later over time. However, the accepted answer was good enough for the person who asked the question – and when I’m asking a question, that’s what my goal is, to get an answer that’s good enough to solve my problem, and move on.
In the Posts table where questions & answers are stored, there’s an AcceptedAnswerId column. If a question has an Id in the AcceptedAnswerId, then that’s the Posts.Id for the answer row.
Let’s try this:
SELECT TOP 100 DATENAME(weekday, pQ.CreationDate) AS DayOfWeek,DATEPART(hh, pQ.CreationDate) AS HourOfDay,AVG(DATEDIFF(mi, pQ.CreationDate, pA.CreationDate)) AS AvgMinutesToAnswer,SUM(1) AS QuestionsAnsweredFROM dbo.Posts pQINNER JOIN dbo.Posts pA ON pq.AcceptedAnswerId = pA.IdWHERE pQ.PostTypeId = 1 /* Question, but not really needed */GROUP BY DATENAME(weekday, pQ.CreationDate),DATEPART(hh,pQ.CreationDate)ORDER BY AVG(DATEDIFF(mi, pQ.CreationDate, pA.CreationDate)),SUM(1) DESC;
The results in the 2018-06 version of the Stack Overflow database:
It’s looking like weekend mornings are the best times to post questions – but even then, it takes a week to get to a good answer! You might think (or at least I did), “Well, if it takes a whole week, does it even make a difference when I post the question?” Let’s flip the sort order and look for the worst times:
That’s kinda wild – it’s weekday afternoons! (We’ll set time zones aside for this, but that’s a whole ‘nother exercise.) That made me wonder: if we only group the data by day of week, what does it look like?
SELECT DATENAME(weekday, pQ.CreationDate) AS DayOfWeek,AVG(DATEDIFF(hour, pQ.CreationDate, pA.CreationDate)) AS AvgHoursToAnswer,SUM(1) AS QuestionsAsked,COUNT(DISTINCT pA.Id) AS QuestionsAnsweredFROM dbo.Posts pQLEFT OUTER JOIN dbo.Posts pA ON pq.AcceptedAnswerId = pA.IdWHERE pQ.PostTypeId = 1 /* Question, but not really needed */GROUP BY DATENAME(weekday, pQ.CreationDate)ORDER BY AVG(DATEDIFF(hour, pQ.CreationDate, pA.CreationDate))
I added a couple more columns because the results are pretty conclusive:
Post your questions on the weekends. Sure, there are way less questions coming in at that time – but that’s also when you get more eyeballs on your questions because you have less competition. You’re more likely to get a good answer, faster, when you’re not competing with other questions.
Is the moral of the story that ChatGPT’s answer was bad? No, or at least, no worse than some of the answers us meatbags came up with initially. I think the key to asking a good data question is to keep following up with more questions. What do the query results show? Where do we think the loopholes are? What’s the real business objective that we’re trying to achieve? How do we gauge the accuracy of an answer?
I’m coming to San Diego on Sept 13-14 for SQL Saturday San Diego!
I’m teaching a one-day pre-conference workshop on Friday, September 13th.
Tuning Databases In One Day – You’ve got production databases in SQL Server or Azure SQL DB, and you want to make ’em faster. You need to identify the database’s bottleneck, prove the root cause, and then recommend fixes. You want to make the right choice for each bottleneck – should you do index changes, query tuning, or server-level settings?
The class will be a mix of 50% slides, and 50% live demos, with plenty of time for Q&A. We’ll even cover 3 sample client findings for the most common performance issues so you can see how I explain the issues to my own clients, and give them proof.
I’m Brent Ozar, and I do this for a living. In one day, I’ll teach you the exact same techniques I use with my clients. I can’t teach you everything about what I do in one day – but I’ll teach you the most important stuff.
Register for the $149 pre-con here, then register for the free SQL Saturday here. Only 100 pre-con tickets are available, so move quickly!
While in a hotel room in Vancouver for PGconf.dev, I strapped on my Apple Vision Pro headset to take your top-voted questions from https://pollgab.com/room/brento. Somehow, the latest Vision OS update gave me a 1980s 3rd Bass haircut.
Here’s what we covered:
What are the best days of the week and times of the day to post a question at StackOverflow.com?
It seems like a simple question, but it’s surprisingly nuanced. I asked ChatGPT’s latest version, 4o, and its answer made me laugh out loud. First off, the T-SQL is terrible: it creates a completely unnecessary temp table. However, a bit of congratulations are in order: at least ChatGPT is trained on the Stack Overflow database schema, and it understood that you have to filter for PostTypeId = 1 for questions (and even put a comment indicating that!)
ChatGPT’s answer assumes that our criteria for a good question is the question’s Score – and I think that’s not a bad assumption to make. After all, a lot of people use Stack Overflow as a game, trying to maximize their reputations. However, let’s assume that we post questions in order to get answers.
I followed up by asking ChatGPT:
What are the best days of the week and times of the day to get good answers quickly?
Again, ChatGPT gets a low score for its generated T-SQL, a high score for understanding the database without additional training, and a flat out failure for botching the query and the results:
So, with that in mind, can you do better than ChatGPT?
Any size version of the Stack Overflow database will work for this exercise. (ChatGPT’s query even fails on the tiny 10GB version.)
For this exercise, I’m not expecting a specific “right” or “wrong” answer – instead, for this one, you’re probably going to have a good time sharing your answer in the comments, and comparing your answer to that of others. Feel free to put your queries in a Github Gist, and include that link in your comments. I’ll check back next week with my thoughts. Have fun!
“The last person must have set it up that way.”
“The last person wrote that code.”
“The last person just didn’t configure it right.”
You can use that excuse for 6 months.For six months, you’re allowed to get up to speed on the company’s politics, the intricacies of the app, and the responsibilities of different departments. You’re allowed to prove yourself to your peers, building up social capital so that they’ll take your recommendations and run with ’em..
You can take your time figuring out whether the last person knew what they were doing or not. You should start by assuming that you did, and give them some benefit of the doubt because they were under time pressure just as you are now. If you’re generous and curious, you can even try to contact them through unofficial channels, offer to buy them lunch, and chat about their experiences at the company.
But after 6 months, the statute of limitations is up.TIME’S UPAfter that, you’re not allowed to blame “the last person” anymore.
The last person can no longer be prosecuted for their crimes against the database, and they are absolved of any guilt. It doesn’t matter who was originally responsible last year: the person responsible is now you.
So pull yourself up by the bootstraps, write up a health check with sp_Blitz, and start working through the problems. Put the correct backups and corruption checking in place. Schedule that outage. Apply the patches. Fix those linked servers using the SA login.
Because after 6 months, if you’re not fixing these problems, the clock is already starting to tick down to when you will be referred to as “the last person”, and they’re going to roll their eyes when they talk about your inability to get the job done.
Just like you’ve been doing about “the last person” for over 6 months.
While in Nashville for a creators conference, I went through your top-voted questions from https://pollgab.com/room/brento.
Here’s what we covered:
Ola Hallengren’s free maintenance solution is widely used as a replacement for SQL Server maintenance plans. It’s a more powerful, flexible tool for backups, corruption checking, and index & statistics updates.
If you’re using it for backups, there are two quick, easy changes that can dramatically reduce your nightly job runtimes.
First, set @NumberOfFiles = 4.SQL Server has internal bottlenecks for single-file backups, and striping your backups across multiple files removes that bottleneck. I’ve written about testing the number of files before, and in a perfect world you’d have the time to do that testing, but for starters, just try writing your backups across 4 files.
You don’t need separate drive targets or network targets – even writing the 4 files to the same volume usually produces pretty dramatic performance improvements for free.
I only do this on my user databases because the system databases are generally so small that it doesn’t matter. To set it up, just right-click on the Agent job for user database full backups, go into the step properties, and add a line for @NumberOfFiles = 4, like this:
This does mean you need all 4 files at restore time, and it means your restore scripts will be a little bit more complicated. But who cares? You’re not writing those scripts by hand in the year 2024, are you? You’re using sp_DatabaseRestore from the First Responder Kit, like a boss, just pointing it at a folder and letting it grab the most recent full, diff, and log scripts:
EXEC [dbo].[sp\_DatabaseRestore] @Database = 'StackOverflow', @BackupPathFull = 'Z:\MSSQL\BACKUP\SQL2022\StackOverflow\FULL\', @BackupPathLog = 'Z:\MSSQL\BACKUP\SQL2022\StackOverflow\LOG\', @TestRestore = 1, @RunCheckDB = 1, @RunRecovery = 1, @Execute = 'Y'
Good job. Moving on – eagle-eyed folks will notice another config change in that screenshot. This next one’s going to be a little more controversial, but hear me out.
Next, set @Verify = ‘N’.By default, Ola’s backup jobs have @Verify = ‘Y’, which means after the backup command finishes, SQL Server reads the whole backup file to check to make sure it’s okay. This can massively extend your backup job times.
I’m not saying you shouldn’t verify your backups! I’m saying don’t verify them from the production box. Instead:
This has a few important performance & reliability benefits:
Presto: those two free, simple changes might cut your backup times by 1/3 or more. You’re welcome!
Is your company hiring for a database position as of June 2024? Do you wanna work with the kinds of people who read this blog? Let’s set up some rapid networking here.
If your company is hiring, leave a comment. The rules:
If your comment isn’t relevant or smells fishy, I’ll delete it. If you have questions about why your comment got deleted, or how to maximize the effectiveness of your comment, contact me.
Each month, I publish a new post in the Who’s Hiring category here so y’all can get the latest opportunities.
Like Sixteen Candles, but different: I go through your top-voted questions from https://pollgab.com/room/brento. Strangely, my video and audio is ever so slightly out of sync in this episode.
Hallelujah. With current versions of Entity Framework, when developers add a mix of parameters and specific values to their query like this:
async Task<List<Post>> GetPosts(int id) => await context.Posts .Where( e => e.Title == ".NET Blog" && e.Id == id) .ToListAsync();
See how part of the filter is hard-coded (“.NET Blog”) while the other part of the filter is dynamically generated, an ID the user is looking for? That causes Entity Framework to generate a query that is partially parameterized:
info: 2/5/2024 15:43:13.789 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (1ms) [Parameters=[@\_\_id\_0='1'], CommandType='Text', CommandTimeout='30'] SELECT [p].[Id], [p].[Archived], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Discriminator], [p].[PublishedOn], [p].[Title], [p].[PromoText], [p].[Metadata] FROM [Posts] AS [p] WHERE [p].[Title] = N'.NET Blog' AND [p].[Id] = @\_\_id\_0
This is the worst of both worlds for SQL Server. If the query was fully parameterized, it’d get plan reuse. If the query wasn’t parameterized at all, we could turn on Forced Parameterization and get plan reuse. However, Forced Parameterization won’t do anything for the above query because SQL Server looks at it and says, “That query’s already parameterized – see, it has a parameter for @__id_0 right there! I’ll just skip it.”
In Microsoft’s example above, ‘.NET Blog’ is a hard-coded string, but the situation is much worse when that is dynamically generated. For every variation of the parameter, SQL Server sees a “new” query coming in. End result: increased CPU to compile “new” query plans, unpredictable plans, plan cache bloat, problems with monitoring tools and Query Store, and more.
Good news! The What’s New in Entity Framework 9 rundown shows a new EF.Parameter method to force parameterization:
async Task<List<Post>> GetPostsForceParameter(int id) => await context.Posts .Where( e => e.Title == EF.Parameter(".NET Blog") && e.Id == id) .ToListAsync();
Which uses a parameter now instead of a hard-coded string:
info: 2/5/2024 15:43:13.803 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (1ms) [Parameters=[@\_\_p\_0='.NET Blog' (Size = 4000), @\_\_id\_1='1'], CommandType='Text', CommandTimeout='30'] SELECT [p].[Id], [p].[Archived], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Discriminator], [p].[PublishedOn], [p].[Title], [p].[PromoText], [p].[Metadata] FROM [Posts] AS [p] WHERE [p].[Title] = @\_\_p\_0 AND [p].[Id] = @\_\_id\_1
Yay! Lower CPU for plan compilation, more reused plans, less memory consumed by redundant plans, and better monitoring tools.
It is a bummer that we have to wait until an estimated November 2024 for EF9 (according to that same What’s New doc), and that developers will have to touch code in order to fix it. I can’t really complain about that, though, because I’m just happy that Microsoft is adding it. EF’s query generation keeps gradually getting better, and that’s awesome.
Every year in May, I look back at when I first registered BrentOzar.com way back in May 2001.
Things were so different back then. I struggled trying to find fun, interesting ways to learn about Microsoft SQL Server. My learning options were really dry books, droning videos sold on DVD, or – brace yourself – the documentation. There weren’t blogs or YouTube videos or open source community scripts on Github.
Today, you have a bewildering number of choices when it comes to learning SQL Server. You can – and should – exhaust all of your free options first, like Google, blog posts, webcasts, and YouTube videos. Take your career as far as you can for free first – the SQL Server community is amazing!
Then, when you’re ready to take your skills and career to the next level, I’m ready for you:
Fundamentals, Yearly$195Save $200* Fundamentals classes
Sign upFundamentals, Lifetime$395Save $300* Fundamentals classes * Buy-now-pay-later available
Sign upFundamentals + Mastering, Yearly$995Save $300* Fundamentals classes * Mastering classes
Sign upThe sale ends May 31. To get these deals, you have to check out online through our e-commerce site by clicking the buttons above. During the checkout process, you’ll be offered the choice of a credit card or buy now pay later.
Can we pay via bank transfer, check, or purchase order? Yes, but only for 10 or more seats for the same package, and payment must be received before the sale ends. Email us at Help@BrentOzar.com with the package you want to buy and the number of seats, and we can generate a quote to include with your check or wire. Make your check payable to Brent Ozar Unlimited and mail it to 9450 SW Gemini Drive, ECM #45779, Beaverton, OR 97008. Your payment must be received before we activate your training, and must be received before the sale ends. Payments received after the sale ends will not be honored. We do not accept POs as payment unless they are also accompanied with a check. For a W9 form: https://downloads.brentozar.com/w9.pdf
Can we send you a form to fill out? No, to keep costs low during these sales, we don’t do any manual paperwork. To get these awesome prices, you’ll need to check out through the site and use the automatically generated PDF invoice/receipt that gets sent to you via email about 15-30 minutes after your purchase finishes. If you absolutely need us to fill out paperwork or generate a quote, we’d be happy to do it at our regular (non-sale) prices – email us at Help@BrentOzar.com.
If you’ve hard-coded installer file names, there’s a big change in this release. There are now just 2 installer scripts: Install-All-Scripts.sql, and a new Install-Azure.sql, which only installs the scripts that are compatible with Azure SQL DB. The old Install-Core scripts are gone because we’ve deprecated sp_AllNightLog, sp_BlitzInMemoryOLTP, and sp_BlitzQueryStore. Read on for why.
Wanna watch me use it? Take the class.To get the new version:
Consultant Toolkit ChangesThis app has been awesomely stable & useful for years, but this month we finally had to pop the hood open to make a few big changes:
That last bullet point refers to the machine where you’re collecting data from, typically your jump VM or laptop. I really do hate minimum requirements, but that desktop runtime is a really fast & easy install that doesn’t require a reboot. It was necessary to get the MFA authentication working.
Just to be super-safe, I’d keep the prior version around so that if you hit a weird client issue, you can still gather data. If you run into any issues, shoot us an email at help@brentozar.com and we’ll work through it with you.
I also updated it to this month’s First Responder Kit, but no changes to querymanifest.json or the spreadsheet. If you’ve customized those, no changes are necessary this month: just copy your spreadsheet and querymanifest.json into the new release’s folder. However, in the Dec 2023 release, we we did update those files, so if you haven’t updated the Consultant Toolkit in a while and you’ve customized it, read that version’s release notes about updating.
sp_Blitz Changes* Enhancement: new checks for SQL Server service & Agent service accounts that have too much permissions. (#3481, thanks Vlad Drumea.)
sp_BlitzFirst Changes* Enhancement: add 2 option for @ExpertMode to skip sp_BlitzWho output. Could already do this with the @OutputResultSets option, but this is much easier to do during emergencies. (#3486) * Enhancement: better startup/failover time detection in Azure SQL DB. (#3504) * Enhancement: new warning about approaching max worker threads. (#3491) * Fix: under heavy load, the headline news wait stats lines would underreport the number of seconds that the sample took. (#3507) * Fix: when the Hekaton “Garbage Collection in Progress” warning fires, mention that it can also be caused by a stressed-out memory-optimized TempDB. (#3488) * Fix: don’t show the plan cache result set if it’s not asked for in @OutputResultSets. (#3492) * Fix: incorrect URL for slow data file reads. (#3483, thanks Matt Mollart.)
sp_BlitzIndex Changes* Enhancement: performance tuning by skipping queries we don’t need depending on the mode. (#3462, thanks Erik Darling.) * Enhancement: skip filtered indexes checks if they don’t have permissions to query sys.sql_expression_dependencies. (#3522) * Fix: restore SQL Server 2014 compatibility. (#3452, thanks Brianc-DBA and others for reporting.) * Fix: unreachable databases are now excluded from the total database count. (#3516, thanks Gary Hunt.) * Fix: using DB_NAME() in more places for consistency. (#3517, thanks Gary Hunt.) * Fix: fixed typos. (#3519, #3515, #3512, thanks Gary Hunt.)
sp_DatabaseRestore Changes* Enhancement: during installation, if they don’t already have Ola’s scripts, explain why they’re about to see warnings. (#3499)
Deprecating sp_BlitzInMemoryOLTP, sp_BlitzQueryStore, and sp_AllNightLogsp_BlitzQueryStore was originally written by Erik Darling when he worked here. He’s moved on to start his own excellent company, plus his own sp_QuickieStore. You should be using that instead.
sp_BlitzInMemoryOLTP was always kinda distributed as a courtesy – the real home for it is in KTaranov’s Github repository, and you can still find it there. It hasn’t been updated in over 6 years, and I’ve never seen anyone using it, so I’m removing it to streamline support issues.
sp_AllNightLog was a ton of fun when we built it several years ago, but it’s consistently had a problem. Companies start using it, then decide they want to build something even more ambitious, typically a C# service with robust error handling and scheduling. sp_AllNightLog isn’t the kind of thing I want to encourage beginners to use – it’s complex.
So when Reece Goding started working on cleaning up sp_BlitzQueryStore’s code, I decided that now was the time to deprecate stuff we no longer use or recommend. You’re definitely welcome to continue to use ’em if you get value out of ’em! I’ve going to move these procs into the Deprecated folder, plus simplify the installation scripts. For the rest of 2024, the only installer script will be Install-All-Scripts.sql.
For SupportWhen you have questions about how the tools work, talk with the community in the #FirstResponderKit Slack channel. Be patient: it’s staffed by volunteers with day jobs. If it’s your first time in the community Slack, get started here.
When you find a bug or want something changed, read the contributing.md file.
When you have a question about what the scripts found, first make sure you read the “More Details” URL for any warning you find. We put a lot of work into documentation, and we wouldn’t want someone to yell at you to go read the fine manual. After that, when you’ve still got questions about how something works in SQL Server, post a question at DBA.StackExchange.com and the community (that includes me!) will help. Include exact errors and any applicable screenshots, your SQL Server version number (including the build #), and the version of the tool you’re working with.
In some of the Query Exercises posts here, you might have noticed links over to similar query exercises at SmartPostgres.com, and the free read-only version of the Stack Overflow database I’m hosting there. I’ve held off the questions for long enough: it’s time to discuss what I’m up to.
First off, BrentOzar.com is staying Microsoft-focused. The ~100K of you who subscribe and visit regularly can breathe a sigh of relief. I’m not going to shove Postgres down your throat, hahaha. From time to time, at the bottom of SQL Server focused posts, I may add a section talking about how Postgres approaches the same problem, but only if I think it’s really interesting learning.
The blog posts, videos, classes, etc here at BrentOzar.com will be for people who work with SQL Server, Azure SQL DB, and Amazon RDS SQL Server every day. My volume of blog posts won’t be declining here – in fact, because I’m reusing the same homework challenges across both SQL Server and Postgres, I’m actually worried about the post count going up, hahaha. I’ve got stuff scheduled out for over a month already.
The new SmartPostgres.com is a separate site that is focused on developers and data professionals who work with PostgreSQL every day. It is NOT “Postgres for SQL Server people.” Don’t subscribe over there unless you use Postgres as part of your job. If you’re only just mildly curious, stay here at BrentOzar.com, because I already overwhelm your in-box and because I’ll include neat Postgres trivia over here.
When I started building SmartPostgres, I decided to host a public-facing Stack Overflow database on AWS Aurora Postgres Serverless because I wanted the simplest possible approach for people to follow along with the blog posts and training exercises. The typical SmartPostgres reader may never actually install a database server, ever.
Over here at BOU, I would absolutely love to host a free public SQL Server with the Stack Overflow database, but there are 4 major problems with that:
So, why Postgres? I think it’s the best-positioned database for the next decade. It’s open source, it’s powerful, all of the cloud vendors offer a flavor of it, and perhaps most interestingly, all of the cloud vendors are trying to extend it in proprietary ways to be the best place to host your Postgres databases. This isn’t some new revelation for me – I’ve believed it for several years, and it’s why we picked Postgres for the SQL ConstantCare back end years ago. Heck, even at Microsoft Build this week, the number of Postgres mentions absolutely dwarfed the mentions of SQL Server.
I am absolutely not telling you that you need to rewrite existing SQL Server apps in Postgres. You don’t. The overall Microsoft data platform is fine (although even still, 2022 isn’t ready), and you could continue using it for years. I certainly plan to keep blogging about it here for years. It’s just that now I’m writing somewhere else, too, for another audience.
I started with audio problems, then had video problems halfway in. It’s amusing because we also discuss feeling like a SQL amateur!
Here’s what we covered:
Eitan Blumin recently wrote a post about the future of the DBA career in which he talked about the rise of jack-of-all-trades IT professionals. It’s a good post. You should read it. I did, and over the last 20 years, I’m pretty sure I’ve read a dozen variations of that post.
There’s a saying: “History doesn’t repeat itself, but it often rhymes.”
To give an example, let’s rewind the clock back to 2008. Business Intelligence was all the rage. SQL Server shipped with Analysis Services, Integration Services, and Reporting Services all inside the box. I remember hearing, “You have to learn all of these tools, or else you’re gonna be a dinosaur. Nobody’s gonna only know the engine anymore. You have to know everything else too, or you won’t get a job.”
So for a while, people were misled into believing they needed to know a little bit about everything. Unfortunately, you only have so many hours per month for learning, and a lot of people wasted a lot of time learning things – only to never actually use those tools.
It took a few years, but the ‘experts’ saying that stuff finally quieted down. The world made it clear that business intelligence and database administration are two different careers – I mean, really, they’re lots of different careers. They just happen to work with data, in the same way that gynecologists and cardiologists both work with the human body.
Today, those ‘experts’ are chanting a new song that just happens to rhyme with the old ones:
Should you learn? Absolutely. But can you learn everything? Absolutely not. Given your limited time, you need a strategy for where your career is going. If you don’t have one, watch my 300-Level Guide to Career Internals.
You have a future. It can be as a database administrator, if that’s what you’re passionate about. It can also be as something else! But don’t let some talking head “expert” tell you what specific technologies you have to learn to stay relevant, because they can’t see the future any better than you can.
Y’all post questions and upvote the best ones at https://pollgab.com/room/brento, and I discuss ’em in my home office before tackling a client’s performance issues:
Here’s what we covered:
In last week’s post, I gave you a trigger that populated a history table with all changes to the Users.AboutMe column. It was your job to write a T-SQL query that turned these Users_Changes audit table rows:
Into the full before & after data for each change, like the earlier query from the blog post:
The trigger will always log the “before” values into the Users_Changes table, but the “after” value could be in a few different places:
To test your own code alongside mine, run these changes:
/* Make some changes: */UPDATE dbo.Users SET AboutMe = 'Update Numero Uno' WHERE Id = 26837;UPDATE dbo.Users SET AboutMe = NULL WHERE Id = 26837;UPDATE dbo.Users SET AboutMe = 'Update Part 3' WHERE Id = 26837;DELETE dbo.Users WHERE Id = 26837;GOSELECT * FROM dbo.Users\_Changes;
I didn’t explicitly call the DELETE scenario out in the challenge, and to be honest, I didn’t expect anyone to think of it when they were writing their answers. The post was all about catching mangled changes to the Users.AboutMe column, and the post’s trigger focused on updates of Users rows, not deletions. However, it’s still perfectly valid for someone to delete a row – and ideally, our query should be able to handle that situation. We didn’t get an answer that returned accurate data in that scenario, and I don’t blame y’all.
It’s an edge case – and it’s even harder of an edge case to handle than it might seem once you factor in NULLs. Let’s think about possible changes:
So that means we can’t just slap a COALESCE in there and call it a day. We’re gonna need some tricky CASE statements.
Here’s the query I came up with, and note that I’m including the different AboutMe_v2 and AboutMe_Current data just for diagnostic purposes so you can see how NULL handling is tricky:
SELECT v1.Id AS Users\_Change\_Id, v1.AboutMe\_Before, v2.AboutMe\_Before AS AboutMe\_v2, u.AboutMe AS AboutMe\_Current, CASE WHEN v2.Id IS NOT NULL THEN v2.AboutMe\_Before WHEN u.Id IS NOT NULL THEN u.AboutMe WHEN u.Id IS NULL THEN '(Unknown, Row Has Been Deleted)' ELSE '(Unknown, My Query Messed Up)' END AS AboutMe\_AfterFROM dbo.Users\_Changes v1 LEFT OUTER JOIN dbo.Users u ON v1.UserId = u.Id LEFT OUTER JOIN dbo.Users\_Changes v2 ON v1.UserId = v2.UserId AND v1.Id < v2.Id AND v1.ChangeDate <= v2.ChangeDate LEFT OUTER JOIN dbo.Users\_Changes vBetween ON v1.UserId = vBetween.UserId AND v1.Id < vBetween.Id AND vBetween.Id < v2.Id AND v1.ChangeDate <= vBetween.ChangeDate AND vBetween.ChangeDate <= v2.ChangeDateWHERE vBetween.Id IS NULLORDER BY v1.ChangeDate;
Which, when auditing this series of changes:
UPDATE dbo.Users SET AboutMe = 'Update Numero Uno' WHERE Id = 26837;UPDATE dbo.Users SET AboutMe = NULL WHERE Id = 26837;UPDATE dbo.Users SET AboutMe = 'Update Part 3' WHERE Id = 26837;DELETE dbo.Users WHERE Id = 26837;
Comes up with these results:
Notice how rows 2 & 3 both have nulls for the v2 and Current columns, but they have different results for AboutMe_After? That’s because the 2nd update statement actually did set AboutMe to NULL, and we do have a Users_Change row that knows about it (from Users_Change_Id = 3.)
In production, if I was faced with this problem, I’d probably want to change the trigger itself to log deleted rows as well as updated ones. However, I found this to be a really fun exercise because I was faced with this exact problem at a client – we were auditing past data whose changes had already been made, and I couldn’t build the historical data retroactively after the changes had been made.
I was chatting with a client’s DBA about this thought-provoking blog post about data governance in the age of generative AI. The DBA’s concern was, “What if we hook up generative AI tools to the production database, and someone asks for, say, recommended salary ranges for our next CEO based on the current CEO’s salary? Will the AI tool basically bypass our row-level security and give the querying person information that they shouldn’t be allowed to see?”
And she had a really good point.
If you haven’t worked with AI tools yet, I don’t blame you. You’ve got a real job, dear reader, unlike me who’s just a consultant who gets to travel all over the world while reading cool blog posts on planes. Anyhoo, since I get to play around a lot more, I’ll give you a quick recap:
So the scenario that scares the hell out of me is:
And the end result is that anyone who queries the LLM suddenly has access to everything that the power user had access to at the time of the LLM training. Anyone with access to the LLM might ask for a list of customers, employee salaries, or whatever.
Me, as pictured by CopilotThat would be what we call “bad.”
Sure, in a sense, this is the same problem data professionals have been struggling with for decades: people can export data, and once it’s out of the database, we don’t have control over who gets to see it. This isn’t new. It’s been the same story ever since Larry Tesler invented copy/paste.
But what’s new is that large language models:
So now, the Amazon blog post explains why smart people are going to burn cycles reinventing row-level security. I’m not saying the blog post is bad – it’s not! It’s just a damn shame that the next 10-20 years of data governance are going to look exactly like the last 10-20 years of data governance.
Spoiler alert: we’ve sucked at this kind of security for decades, and we will in the next decade, too.
Historically, Microsoft publicly announces the next version of SQL Server about a year before it ships. For example:
This was pretty consistent with past releases: about a year before the release, Microsoft would go on record with the features included with the next version, and some companies would be able to start using it in production to prepare for the eventual release.
Well, it’s been about 2.5 years since SQL Server 2022’s announcement, so we’re looking at at least a 3-year release cycle. We might be coming up on the announcements for SQL Server 2025.
They could either announce the next version’s features at Microsoft Build on May 21-24 or at the PASS Summit on Nov 4-8. I’ll be watching the Build session catalog as Microsoft updates it because when Microsoft announces a new product, the related sessions are hidden until after the product is officially announced. It’s free to attend Build online, so if they announce 2025 during a keynote, I’ll tune in to the related general sessions.
I personally love a 3-year release cadence, and I don’t want new versions any more frequently than that. People have a hard time upgrading, and frankly, Microsoft has a hard time shipping that quickly. Just release every 3 years, and make sure the product is actually ready at the time of release. That’s good enough.
I’ve heard some folks say, “Maybe Microsoft is done with SQL Server because they want everyone to move to Azure.” Well, I’m sure they do, but they also acknowledge the reality that many companies host their infrastructure in Amazon and Google. Those clients need SQL Server licensing, because you can’t practically run Azure SQL DB in other clouds.
Microsoft also has to keep releasing new versions of SQL Server because they need to keep cashing your sweet, sweet Software Assurance checks. While SA does come with other benefits, like free licensing for a couple/few passive standby servers for HA and DR, those benefits aren’t priceless. If Microsoft didn’t ship a new version for, say, 5 years, I know some companies that would simply stop paying Software Assurance. Even if these companies don’t actually upgrade that often, they pay for the ability to know that they could upgrade – which means Microsoft has to keep bringing out versions & features.
What might Microsoft add in 2025? Clearly, AI is the buzzword du jour, and Microsoft’s Joe Sack has already demoed some of the Copilot things they’re working on in Azure SQL DB. Copilot integration requires internet connectivity, something that’s easier to integrate in cloud databases like Azure SQL DB. If you try to do large language model work on-premises, you’ll quickly learn that it’s extremely CPU and GPU intensive, and that doesn’t really make sense for a product like SQL Server that’s licensed by the CPU core. I’ll be curious to see how they package AI services to a crowd that sometimes screams in horror at the idea of giving their database servers access to the Internet.
Would you be okay giving your SQL Servers access to the Internet? And what AI-related features would you love to see included?
Your challenge for this week was to find out who keeps mangling the contents of the AboutMe column in the Stack Overflow database.
Conceptually, there are a lot of ways we can track when data changes: Change Tracking, Change Data Capture, temporal tables, auditing, and I’m sure I’m missing more. But for me, there are a couple of key concerns when we need to track specific changes in a high-throughput environment:
For example, in this week’s query challenge, the Users table has a lot of really hot columns that change constantly, like LastAccessDate, Reputation, DownVotes, UpVotes, and Views. I don’t want to log those changes at all, and I don’t want my logging to slow down the updates of those columns.
Furthermore, I probably don’t even want to capture the entire before or after values of the AboutMe column, either. It’s NVARCHAR(MAX), which can be huge. Depending on what “mangling” means, I might only need to grab the first 100 characters, or the length of the before/after changes. That’ll reduce how much I store, too, important in high-transaction environments that push the data to several Availability Group replicas.
Let’s say here’s the data I decided to gather:
CREATE TABLE dbo.Users\_Changes(Id INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, UserId INT NOT NULL, ChangeDate DATETIME2, AboutMe\_Before NVARCHAR(100), AboutMe\_After NVARCHAR(100), AboutMe\_Length\_Before BIGINT, AboutMe\_Length\_After BIGINT, HostName NVARCHAR(128), AppName NVARCHAR(128), SystemUser NVARCHAR(128))GO
You could refine that further by capturing less data if you knew exactly how the data was being mangled. For example, if the mangler always sets the rows to null or to short strings, you wouldn’t have to gather the before/after contents – just the length would be enough.
You could also get a little fancier by only storing the “before” data, but I kept it simple and just logged both before & after here for simplicity’s sake. (Spoiler alert: the next Query Exercise at the end of this post is going to be a related challenge.)
To populate this data, I love triggers. Here’s the one I came up with after a little testing:
CREATE OR ALTER TRIGGER dbo.Users\_Update\_Audit ON dbo.Users AFTER UPDATE ASBEGINSET NOCOUNT ON;INSERT INTO dbo.Users\_Changes (UserId, ChangeDate, AboutMe\_Before, AboutMe\_After,AboutMe\_Length\_Before, AboutMe\_Length\_After,HostName, AppName, SystemUser)SELECT i.Id, GETDATE(),LEFT(d.AboutMe, 100), LEFT(i.AboutMe, 100),LEN(COALESCE(d.AboutMe,'')), LEN(COALESCE(i.AboutMe,'')),HOST\_NAME(), APP\_NAME(), SYSTEM\_USERFROM inserted iINNER JOIN deleted d ON i.Id = d.IdWHERE COALESCE(i.AboutMe,'') <> COALESCE(d.AboutMe,'')ENDGO
The actual query plan on an update is nice and quick. A few things about the code that may not be intuitively obvious:
If you knew the exact kind of mangling that was happening, you could refine the WHERE clause even further, looking for specific data patterns.
Here’s what the output table contents look like after changing a few rows:
Nice and simple, and makes for really easy investigations. Just make sure to drop the trigger and the change table when you’re done! I had a really hearty laugh at one client when I returned a year later and they still had both in place.
Other SolutionsTom aka Zikato aka StraightforwardSQL commented with a pointer to an awesome blog post he’s written on this very topic! He compared & contrasted a few different solutions, and then ended up with a hybrid solution involving a trigger, XE, and Query Store.
Connor O’Shea wrote an extended events session that filters query text based on its literal contents. I get a little nervous about that kind of thing, because they often miss weirdo situations (like synonyms and aliases), and they’re a little tricky to debug. For example, his first iteration also caught selects – something that would be terrible on a production system. It’s a good starting point though.
Erik Darling used temporal tables to capture the changes, and I especially love his security disclaimer. I don’t have any experience with login impersonation either – it’s funny, but my contract actually prohibits any security work whatsoever. I hate security work.
ChatGPT is helpful for tasks like this, even for a starting point if you don’t end up actually using the code. I asked ChatGPT to do this for me, and it came up with different results, but a good starting point nonetheless.
Your Next Query Challenge:Getting Data Out of a Fancier TriggerThe above process works great if we’re willing to store both the before AND after values, but what if we’re dealing with a really high-throughput system with a ton of changes per second, and we want to avoid storing the “after” values each time? The table & triggers would look like this:
DROP TABLE IF EXISTS dbo.Users\_Changes;GOCREATE TABLE dbo.Users\_Changes(Id INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, UserId INT NOT NULL, ChangeDate DATETIME2, AboutMe\_Before NVARCHAR(100), AboutMe\_Length\_Before BIGINT, HostName NVARCHAR(128), AppName NVARCHAR(128), SystemUser NVARCHAR(128))GOCREATE OR ALTER TRIGGER dbo.Users\_Update\_Audit ON dbo.Users AFTER UPDATE ASBEGINSET NOCOUNT ON;INSERT INTO dbo.Users\_Changes (UserId, ChangeDate, AboutMe\_Before, AboutMe\_Length\_Before,HostName, AppName, SystemUser)SELECT i.Id, GETDATE(),LEFT(d.AboutMe, 100), LEN(COALESCE(d.AboutMe,'')),HOST\_NAME(), APP\_NAME(), SYSTEM\_USERFROM inserted iINNER JOIN deleted d ON i.Id = d.IdWHERE COALESCE(i.AboutMe,'') <> COALESCE(d.AboutMe,'')ENDGO
Now, when we want to get data out to see the before & after values, it gets a little trickier. Say we run 3 update statements, and then check the value of the change table:
UPDATE dbo.Users SET AboutMe = 'Update Numero Uno' WHERE Id = 26837;UPDATE dbo.Users SET AboutMe = NULL WHERE Id = 26837;UPDATE dbo.Users SET AboutMe = 'Update Part 3' WHERE Id = 26837;SELECT * FROM dbo.Users\_Changes;
The resulting query only shows the before data, but not the change that was actually affected during that update statement:
For example, notice that the last update above set the contents to Update Part 3, but that value doesn’t show in the screenshot. That’s your challenge: I want you to write a SELECT query that reproduces the full before & after data for each change, like the earlier query from the blog post:
Share your answer in the comments, and feel free to put your queries in a Github Gist, and include that link in your comments. I’ll check back in next week with answers & thoughts. Have fun!
The short story: SQL Server 2019 appears poised to swallow the SQL Server market altogether, hahaha.
The long story: ever wonder how fast people are adopting new versions of SQL Server, or what’s “normal” out there for SQL Server adoption rates? Let’s find out in the winter 2023 version of our SQL ConstantCare® population report.
Out of the thousands of monitored SQL Servers, SQL Server 2019 is now at 48% of the market! That’s the highest percentage we’ve seen for any version in the 3 years that we’ve been doing this analysis, up from last quarter’s 44% market share. Here’s how adoption is trending over time, with the most recent data at the right:
SQL Server 2019 still continues to grow while everything else shrinks, with the exception of 2022 treading water:
How big are typical SQL Servers?Let’s group the market together roughly into quarters:
For a long time in the SQL Server space, people have used the term VLDB to denote a very large database, and we’ve usually marked 1TB as the town borders of VLDBville. Today, given that about 1/4 of all SQL Servers host 1TB or more, and given how fast modern storage is able to back up 1TB databases, that 1TB threshold is less meaningful.
That large number of small servers means the CPU distribution is also fairly small:
As is the memory target distribution:
Although if you slice & dice by data size, then that changes the CPU & memory numbers differently. The number of CPU cores people typically use for >3TB SQL Servers is very different than the core count for, say, 50-250GB servers. And now if you’ll excuse me, I gotta do a whole bunch of slicing and dicing because I share that data with clients when we’re analyzing their SQL Servers during a SQL Critical Care®.
Back at home in the office, time to settle in with a nice caffeine-free Diet Coke and go through your top-voted questions from https://pollgab.com/room/brento. Why caffeine-free? Because I slug multiple coffees first thing in the morning when I wake up (usually around 3am-4am), and by the time I stream with y’all, I don’t need any more go juice.
Here’s what we covered:
Is your company hiring for a database position as of May 2024? Do you wanna work with the kinds of people who read this blog? Let’s set up some rapid networking here.
If your company is hiring, leave a comment. The rules:
If your comment isn’t relevant or smells fishy, I’ll delete it. If you have questions about why your comment got deleted, or how to maximize the effectiveness of your comment, contact me.
Each month, I publish a new post in the Who’s Hiring category here so y’all can get the latest opportunities.
For this week’s Query Exercise, your challenge is to find out who keeps messing up the rows in the Users table.
Take any size version of the Stack Overflow database, and the Users table looks like this:
People are complaining that from time to time, the contents of the AboutMe column in some – but not all – of the Users rows are getting mangled. We’re not sure if it’s an app bug, an ETL problem, or someone goofing around in T-SQL, and we need you to find out.
At the same time, this is also a busy production database, and we want to minimize impacts to the end users. The site still needs to go fast.
For this exercise, I’m not expecting a specific “right” or “wrong” answer – instead, for this one, you’re probably going to have a good time sharing your answer in the comments, and comparing your answer to that of others. Feel free to put your queries in a Github Gist, and include that link in your comments. I’ll check back in next week with the approach I usually use with clients. Have fun!
I first registered BrentOzar.com way back in May 2001.
Over the years, some things in databases have changed a lot, but some things still remain the same.
Every year, people get handed applications and databases, and they’re told, “The back end is Microsoft SQL Server. Figure it out and make it work – faster.”
Every year, people struggle. They try to learn as much as they can via Google, free blog posts and webcasts, and their peers. They put the pieces together as best they can, but eventually, they hit a wall.
And that’s where my classes come in.
Fundamentals, Yearly$195Save $200* Fundamentals classes
Sign upFundamentals, Lifetime$395Save $300* Fundamentals classes * Buy-now-pay-later available
Sign upFundamentals + Mastering, Yearly$995Save $300* Fundamentals classes * Mastering classes
Sign upThe sale ends May 31. To get these deals, you have to check out online through our e-commerce site by clicking the buttons above. During the checkout process, you’ll be offered the choice of a credit card or buy now pay later.
Can we pay via bank transfer, check, or purchase order? Yes, but only for 10 or more seats for the same package, and payment must be received before the sale ends. Email us at Help@BrentOzar.com with the package you want to buy and the number of seats, and we can generate a quote to include with your check or wire. Make your check payable to Brent Ozar Unlimited and mail it to 9450 SW Gemini Drive, ECM #45779, Beaverton, OR 97008. Your payment must be received before we activate your training, and must be received before the sale ends. Payments received after the sale ends will not be honored. We do not accept POs as payment unless they are also accompanied with a check. For a W9 form: https://downloads.brentozar.com/w9.pdf
Can we send you a form to fill out? No, to keep costs low during these sales, we don’t do any manual paperwork. To get these awesome prices, you’ll need to check out through the site and use the automatically generated PDF invoice/receipt that gets sent to you via email about 15-30 minutes after your purchase finishes. If you absolutely need us to fill out paperwork or generate a quote, we’d be happy to do it at our regular (non-sale) prices – email us at Help@BrentOzar.com.
On November 4 in Seattle, I’m presenting a new pre-conference workshop!
Tuning T-SQL for SQL Server 2019 and 2022
You’ve been working with SQL Server for a few years, and you’re comfortable writing queries and reading execution plans.
Your company is now using SQL Server 2019 or 2022 in production, and you want your queries to go as fast as possible. You want to know what changes to make to your existing queries in order to speed them up dramatically. You don’t want a “what’s new” session – you want practical information you can use to identify T-SQL that used to work fine in older versions, but now needs attention in newer versions.
In this 1-day session, Brent Ozar will use the same practical before-and-after techniques that he uses in his Query Challenges blog series in order to demonstrate what parts of your skills need to change as you modernize your databases.
You should be comfortable using SSMS to write multi-page queries, functions, and stored procedures. You should be comfortable identifying common query plan operators like index seeks & scans, key lookups, sorts, and parallelism, and comparing plans.
In this session, you’ll:
Attendees will get one year free access to my Fundamentals of Query Tuning and Mastering Query Tuning classes, both of which will be updated with content from the pre-con.
Register here, and see you in Seattle!
As my time in Hong Kong came to an end, I sat inside on a foggy morning and hit your top-voted questions from https://pollgab.com/room/brento.
Here’s what we covered:
I’m kinda weird. I get excited when I’m troubleshooting a SQL Server problem, and I keep hitting walls.
I’ll give you an example. A client came to me because they were struggling with sporadic performance problems in Azure SQL DB, and nothing seemed to make sense:
During a bio break, I unloaded the dishwasher (it’s what I do) and made a mental list of things that would cause blocking, but not on indexes, and not show up in the plan cache. And that’s when it hit me, just as I was unloading the wine glasses. (Don’t judge me: I drink a lot, and I don’t have time for hand-washing.)
sp_getapplock lets developers use SQL Server’s locking mechanisms for their own purposes, unrelated to tables. Let’s say you have an ETL process, and you want to make sure that it can only be run from one session at a time. You’d start the process by running:
EXEC sp\_getapplock @Resource = 'ETL Process',@LockMode = 'exclusive',@LockOwner = 'session'
That’s kinda like saying BEGIN TRAN, but for entire processes. If someone else tries to run the same query and grab a lock from their session, they’ll get blocked. Here’s what it looks like from sp_BlitzWho:
Session 63 grabbed the lock first, and session 70 is trying to get it, but they’re waiting on LCK_M_X because 63 has an eXclusive lock on it. Note that 63’s query_text shows sp_getapplock – that’s where the troubleshooting Eureka moment hits.
When session 63 is done, it can release the lock like this:
EXEC sp\_releaseapplock@Resource = 'ETL Process',@LockOwner = 'session'
If you work like I do, troubleshooting things after they’ve happened, without the ability to set things up in advance of the problem, this is extraordinarily hard to track down.
Once you know about the problem, you can monitor blocked processes in Azure SQL DB (shout out to Etienne Lopes for a well-written, start-to-finish tutorial, and you should subscribe to his blog.) Etienne uses a file target there, but I used a ring buffer target because I didn’t have access to the client’s Azure file systems. The ring buffer target is way easier to set up:
CREATE EVENT SESSION [BlockedProcesses] ON DATABASEADD EVENT sqlserver.blocked\_process\_report()ADD TARGET package0.ring\_buffer(SET max\_memory=(10240))WITH (STARTUP\_STATE=ON)GOALTER EVENT SESSION [BlockedProcesses] ON DATABASE STATE = START;GO
The problem is that Azure SQL DB’s blocked process threshold is 20 seconds, and can’t be changed.
EXEC sp\_BlitzWho@OutputDatabaseName = 'mydbname',@OutputSchemaName = 'dbo',@OutputTableName = 'BlitzWho'
Once you’ve identified that there’s a blocking chain led by sp_getapplock, the database side of the tuning work is done. There’s no database-side “tuning” on blocking like this: it’s up to the developers to use conventional transaction-based tuning processes, but in whatever app code is calling sp_getapplock. The app is essentially running its own transaction, so the developers need to:
With those tips, sp_getapplock can be completely fine. It’s not like sp_getapplock is inherently bad, any more than BEGIN TRAN is bad. It’s just that detecting its long transactions is way harder.
The Monday after next, I’ll be getting together with dozens of smart people who are a lot like you. You’ve attended some of my free online streams, read my blog posts, and you use the First Responder Kit. You enjoy my laid-back, humorous approach to sharing what I’ve learned over the years.
You love live classes. You’ve tried watching recordings or stepping through demos yourself, but…you just can’t block out the time, and you can’t stay motivated while you’re watching a recording. You like the fast-paced energy of seeing me live, asking me questions, and seeing me respond to your chats.
You hate free conferences that feel like Zoom meetings. You’ve tried attending a few online events, but they felt just like sitting through one meeting after another. They didn’t dive deeply into any one subject – they just hopped around from one topic to another, and many of those topics just weren’t relevant to you. They were good for free stuff, but…
You’re ready for Fundamentals Week! You’ll spend the week with me learning about indexes, query tuning, columnstore, how I use the First Responder Kit, and more.
The conference dates are May 6-10, 2024, and classes are 10AM-6PM Eastern, 7AM-3PM Pacific. Here’s what we’ll cover:
Register now for $1,595. To keep prices low, this price does not include the class recordings. If you want those, during checkout, you’ll see an offer to add lifetime access to my Recorded Class Season Pass Fundamentals for just $395. See you in class!
I recently went to Shanghai & Hong Kong, and stopped to take your questions from https://pollgab.com/room/brento while sitting next to the Hong Kong harbor.
Here’s what we covered:
Post your Azure SQL DB and SQL Server questions at https://pollgab.com/room/brento and upvote the ones you’d like to see me discuss. In this episode, I’m overlooking Breiðamerkursandur, Diamond Beach, one of the most Instagrammed places in Iceland. Here’s a shot from a prior trip when I climbed up on one of the icebergs: This time...
I got an interesting request for consulting, and I’m going to paraphrase it: We were using Azure SQL DB with automatic index tuning enabled for months. Things were going great, but… we just deployed a new version of our code. Our deployment tool made the database schema match our source control, which… dropped the indexes...
Pinal Dave recently ignited a storm of controversy when he quizzed readers about which one of these would be faster on AdventureWorks2019: [crayon-64feb6666bf59548644264/] I laughed so hard when I saw the storm of responses on Twitter. People sure do get passionate about this kind of thing. If you ever wanna witness patience and generosity in...
Post your Azure SQL DB and SQL Server questions at https://pollgab.com/room/brento and upvote the ones you’d like to see me discuss. In this episode, I’m at Jökulsárlón Glacial Lagoon, a wonder of nature, watching the icebergs pile up, stuck in the lagoon due to the incoming high tides.
Here’s what we covered:
Road trip time! I’m recovering from jet lag after flying over to Iceland, so I went through your top-voted questions from https://pollgab.com/room/brento.
Here’s what we covered:
Is your company hiring for a database position as of September 2023? Do you wanna work with the kinds of people who read this blog? Let’s set up some rapid networking here.
If your company is hiring, leave a comment. The rules:
If your comment isn’t relevant or smells fishy, I’ll delete it. If you have questions about why your comment got deleted, or how to maximize the effectiveness of your comment, contact me.
Each month, I publish a new post in the Who’s Hiring category here so y’all can get the latest opportunities.
On August 30, Azure’s Australia East data center had a big problem, affecting customers like Bank of Queensland and Jetstar. Here’s the timeline:
Note that 11:34, the decision was made to shut down infrastructure without Microsoft failing your databases over elsewhere. If you were an Azure SQL DB or Cosmos DB user, and you weren’t paying for replicas in another data center, it was up to you to follow Microsoft’s disaster recovery guidance.
Controversial opinion: I actually love that and I think it’s great.
I see a lot of Azure SQL DB users make the mistake of assuming that Azure includes disaster recovery, but it does not. It’s on you, and as a result, you save money. (Same thing in AWS Aurora PostgreSQL.) I’m sure there are plenty of small business databases that don’t need disaster recovery within a day or two. Heck, even Bank of Queensland probably has some databases that fit into that category, although… probably not as many as actually went down, hahaha.
There’s a problem with that, though: Microsoft didn’t notify affected customers about which of their databases were down, or that the customers should start their DR processes. Microsoft couldn’t notify customers because … they didn’t know who those customers were. Microsoft’s Azure status history doesn’t let you easily link to a single event, but if you expand the outage on 30 Aug, the preliminary writeup is really detailed, and explains why they were flying blind:
From a SQL perspective… Some databases may have been completely unavailable, some would have experienced intermittent connectivity issues, and some databases would have been fully available. This uneven impact profile for databases in the degraded ring, meant that it was difficult to summarize which customers were still impacted, which continued to present a challenge throughout the incident.
Boy, I have been there. When multiple databases and servers go down, one of the first thing management wants to know is, “Which specific apps are down?” When you can’t answer that question, it makes management pretty nervous, and adds even more stress to the situation.
As we attempted to migrate databases out of the degraded ring, SQL did not have well tested tools on hand that were built to move databases when the source ring was in degraded health scenario. Soon this became our largest impediment to mitigating impact.
It might be tempting to point and say, “Well, Microsoft, you should have that” – and they should – but I don’t see a lot of shops with well-tested automated DR failover tools.
I’ve long said that Azure SQL DB does a better job of database administration than not having a DBA altogether, and this is a good example. Customers who didn’t have a DBA wouldn’t have been any better off managing their own DR in a situation like this, and frankly, most customers who do have a DBA wouldn’t have been better off either. (If you smugly think you’d be fine, point to your current list of production servers & databases, and prove that every single database you have is also synced with DR. Go ahead. I’ll wait.)
Because every DB moved required manual mitigation via scripts, it seriously undermined our ability to move fast even once impacted DBs were identified, and DB moves were scheduled.
Elsewhere in the post, they mention that over 250,000 databases were involved in just one of these troubled rings of databases alone. You just can’t manually do anything with 250,000 databases, so I can only imagine how stressful it was to try to write the automation code under fire. Props to the folks working that night.
Overall, this kind of incident – and how Microsoft responded to it afterwards – is why I think that if you don’t have a DBA, you could do a lot worse than relying on Microsoft, Amazon, and Google doing that job for you instead. Platform-as-a-Service lets someone else stress out about the outage, troubleshoot it as quickly as they can, then build better processes to shorten the next outage.
After consuming waaaay too much coffee, I went through your top-voted questions from https://pollgab.com/room/brento:
Here’s what we covered:
Today’s webcast featured an awful lot of questions from one particular person who was super-active at https://pollgab.com/room/brento: Here’s what we covered: 00:00 Start 00:03 Miles: Hi Brent, I am aware of “sp_statement” completed and sp_batch_completed events in trace and extended events. But, what is the use of rpc_completed event? How this event will be helpful...
You’ve been getting more and more deadlock errors, and users are starting to complain. You’re wondering if your queries are the root cause, and if so, what you should do to fix ’em. In a fast-paced session, I’ll explain the 3 causes for deadlocks, and the 3 ways to get relief. We’ll use 2 demo...
Today, I need your help with some of the top-voted questions from https://pollgab.com/room/brento. Chime in in the comments: 00:00 Start 00:52 Accidental DBA: Hello Brent. Thank you for keeping your Q& A sessions entertains and informative. My question is non-technical…Have you ever said this to your client? You keep using that word. I don’t think...
Today’s batch of questions from https://pollgab.com/room/brento requires some really long answers, and some are right to the point. 00:00 Start 02:13 Eh? Aye…: As DBAs, how can we best prepare for the AI world in terms of data? Should we start with AI as a concept fundamentals, or what ua the ‘data type’ we should...
Turns out y’all actually work over the summer – there are actually cool new features this month! I think I’m actually going to have to record updated sp_BlitzIndex, sp_BlitzLock, and sp_BlitzQueryStore modules for my “How I Use the First Responder Kit” class because these features are pretty awesome. To get the new version: Download the...
We headed up to the mountains to get away from the Vegas heat. Before the sun rose, I took your top-voted questions from https://pollgab.com/room/brento. Here’s what we covered: 00:00 Start 00:36 Ozan: Hi Brent, how should Volume Snapshot Backups (VSS) be configured correctly that it will not freeze the database’s IO for a couple...
I took a break from packing my bags long enough to answer your top-voted questions from https://pollgab.com/room/brento. Here’s what we covered: 00:00 Start 02:42 T-man: Hi BO. A friend has multiple Distributed AGs (on separate business division servers) but needs to share ODS data with each of the DAG DBs. Any near-real time architecture ideas...
This year’s PASS Data Community Summit is November 13-17 in Seattle. (No online version is available this year – you gotta be there in person.) If you’ve attended in person before, you already know the value: it’s like a family reunion for the Microsoft data community. It’s your chance to see the authors you’ve read...
I went through the top-voted questions from https://pollgab.com/room/brento and discussed ’em live on my Twitch channel. Here’s what we covered: 00:00 Start 00:39 tanchenglai: Hi Brent! We bought your Level 2 Bundle last year. Recently, we went into frequent thread pool issues; we suspect it is due to high CPU utilization. Our IT manager...
Y’all posted so many good questions at https://pollgab.com/room/brento that I went longer than usual this time: Here’s what we covered in this episode: 00:00 Start 02:17 Tanchenglai: Hi Brent, recently we always ran into thread pool issue. I checked Performance Dashboard and found that CPU spiked over 80% occasionally. I suspect apps can’t connect after...
While on a road trip, Richie stopped by a used bookstore (or would that be used book store?) and made an amusing observation about the relative value of different SQL Server books. I just had to go look at Amazon for some research. I’m listing prices below, but keep in mind that when you click,...
I step away from the backyard long enough to take y’all’s questions from https://pollgab.com/room/brento. Here’s what we covered in this episode: 00:00 Start 02:23 Stockburn: Hi Brent, we are using distributed always on AGs, with a node in AWS for DR. We are thinking of using this to quickly migrate the work load to the...
There are only a couple weeks left in this summer’s marathon of Office Hours sessions, which means there’s limited time left to ask your toughest database problems (not trivia questions) at https://pollgab.com/room/brento. Here’s what we covered in this episode: 00:00 Start 03:22 tanchenglai: Hi Brent, I have implemented a system that records data in another...
Is your company hiring for a database position as of August 2023? Do you wanna work with the kinds of people who read this blog? Let’s set up some rapid networking here. If your company is hiring, leave a comment. The rules: Your comment must include the job title, and either a link to the...
I took your questions from https://pollgab.com/room/brento before heading out to pick up my Cadillac from the mechanic. Here’s what we covered: 00:00 Start 04:50 JudgeDredd: Hi Brent, have you ever been asked for opinion/expertise (as a database specialist) in a court trial? If yes, could you share some details? 07:10 Aart Bluestoke: A maximum of...
Post your SQL Server and Azure SQL DB questions at https://pollgab.com/room/brento and upvote the ones you’d like to hear me discuss. Here’s what we discussed in this episode: 00:00 Start 02:47 therealprodDBA: In the last webcast, I heard you cite that majority of your clients are on AWS. I got curious now that my shop...
To celebrate this month’s launch of our new class, Fundamentals of Azure Networking for the Data Professional, we’re making one video a day completely free. Here’s what’s on tap this week: July 24: Demo: Working With a VPN July 25: Hub and Spoke vs Mesh Design Patterns July 26: Microsoft-Managed Virtual Networks July 27: Demo:...
Seriously, how do y’all keep coming up with so many great questions? I went through your top-voted questions from https://pollgab.com/room/brento/ on a live stream on my Twitch channel, like I do on most Wednesdays & Thursdays, and really enjoyed these: Here’s what we covered: 00:00 Start 04:14 Groove_timer: Hey Brent, Trying to convince my employer...
Stack Overflow was down, so y’all posted questions at https://pollgab.com/room/brento and I gave ’em my best shot. Here’s what we covered: 00:00 Start 04:33 Amir: Does Az-900 worth the effort and money? What about other microsoft certifications? 06:16 Mr. M: Hi Brent. Which feature in SQL Server you like the most? 08:43 Q-Ent: Hi Brent,I...
The short story for this quarter: SQL Server 2022 adoption rates have stalled, even backtracked, and it doesn’t appear to be due to the cloud, either. For the long story: ever wonder how fast people are adopting new versions of SQL Server, or what’s “normal” out there for SQL Server adoption rates? Let’s find out...
To celebrate this month’s launch of our new class, Fundamentals of Azure Networking for the Data Professional, we’re making one video a day completely free. Here’s what’s on tap this week: July 17: Demo: Using Virtual Networks for Data Services July 18: Demo: Private Networks Without Public Access July 19: Demo: Multiple Private Networks July...
Almost all of today’s questions from https://pollgab.com/room/brento were great! Well, except two. Here’s what we covered: 00:00 Start 02:13 Andrei: What’s the appropriate response/punishment for developers who insist on storing json and xml in nvarchar(max) fields? 03:43 JustGoogleIt: What are your thoughts on Jeff Moden’s “Black Arts” Index Maintenance — GUIDs v.s. Fragmentation? How would...
Raise your hand if you’ve ever blamed the network. You’re responsible for the health, security, and uptime of your company’s data services in Azure. You’ve provisioned a few services, but every now and then, you run into problems making your services reachable and reliable from different users and app servers. You want to understand: Your...
The first time I saw FOR XML PATH being used to generate a comma-delimited list, I think I stared at it, shook my head to clear the cobwebs, stared at it some more, and then closed the code editor thinking it was complete witchcraft. And that same thing probably happened the next several times, too....
Y’all posted & upvoted questions at https://pollgab.com/room/brento, and we finished up the session talking about the relationships between developers and database administrators. Here’s what we covered: 00:00 Start 02:35 Nortzi: Hi Brent. Is there a way to optimize sorting a result using a column from a different table? Using POC index pattern (partition,order,covering) works great...
At https://pollgab.com/room/brento, post the questions you’d like to get my opinion on, and I’ll take a break every now and then at the office and go through ’em. Here’s what we covered in this episode: 00:00 Start 01:08 #ARRRRRGH: Hi Brent. Have you seen anybody put good visualisations on top of sp_Blitz? Im trying to...
Today’s live stream was a little different: I demonstrated using the Aiomatic WordPress plugin, ChatGPT, and Azure Open AI to write blog posts. I showed the kinds of content it writes, the kinds of blog post content it doesn’t include, and taught you how to identify meaningless word salad blog posts written by people who...
I can finally see the finish line in my backyard renovation, now that the concrete’s going in. I took a break from watching the construction folks to go through your highly upvoted questions at https://pollgab.com/room/brento: Here’s what we covered: 00:00 Start 02:03 Fabricator: Hi Brent, what is your take on the recent lunch of Microsoft...
You’ve got an existing application with a database back end. You’re thinking about changing the database, and you don’t wanna break stuff. The most important thing to understand is difference between constructive and destructive changes, also known as additive and destructive changes, or non-breaking and breaking changes. Constructive change examples: adding a new table or...
Sometimes, I don’t care if questions get a lot of upvotes at https://pollgab.com/room/brento – I just don’t wanna answer ’em. They’re not necessarily bad questions, but I’m just not interested in them, or I’ve answered them repeatedly, or I don’t have a good answer. Maybe y’all do, though, so I’ve numbered these. If you want...
I start today’s Office Hours by playing with the very underwhelming Github Copilot in ADS, then take your questions from https://pollgab.com/room/brento. Here’s what we covered: 00:00 Start 04:06 Github Copilot in Azure Data Studio 24:36 Byron: Seeing plan cache queries for a long since retired table. What is best way to track down the app...
This one’s a pretty quiet release: just bug fixes in sp_Blitz, sp_BlitzLock, and sp_DatabaseRestore. To get the new version: Download the updated FirstResponderKit.zip Azure Data Studio users with the First Responder Kit extension: ctrl/command+shift+p, First Responder Kit: Import. PowerShell users: run Install-DbaFirstResponderKit from dbatools Get The Consultant Toolkit to quickly export the First Responder Kit...
I have been absurdly lucky to be around so many amazing and inspirational managers. Almost everything I know was taught to me by someone else. Over my life, I’ve learned so much about careers, marketing, business, communications, teaching, databases, you name it. For this month’s T-SQL Tuesday, Gethyn Ellis asked us to share the best...
One of the things I love about SQL ConstantCare® (which happens to be free this month, by the way) is that I can keep in touch with what y’all are actually doing on your production servers. Today’s lesson was a complete shocker to me: some of y’all are changing the compatibility level on the master...
It’s back to lighter fog-friendly clothes as the ship pulls into San Francisco and I answer your questions from https://pollgab.com/room/brento. Here’s what we discussed: 00:00 Start 01:16 Pozzolan: Hey Brent, My companies budget for SQL licensing is small. In such situations, does it make sense to switch or start all new development on something like...
One of the big principles of our SQL ConstantCare® monitoring product – which happens to be free this month – is that we only wanna tell you things you’re actually gonna take action on. Anytime we email you advice, there a few “Mute” links that you can click on to never hear this advice. You...
Last week, I asked if y’all had been to in-person regional events before, and whether you were going back. I wanted to know because Data Saturdays and SQL Saturdays are starting to happen again, but … attendance seems way down from pre-COVID numbers. I wondered, are people just not going to return to conferences for...
I stopped piloting the ship long enough to answer your top-voted database questions from https://pollgab.com/room/brento. Here’s what we covered: 00:00 Start 00:42 Dirty_Pages: You mentioned recently how much you swear, what is your favorite swear word(s) and why? Any favorite insults? 02:38 SQLCircus.Clown: What is your go to/default SQL datatype for storing a run of...
Thankfully, we didn’t hit any icebergs or glacier droppings, so I put my comfiest coat on to answer your top-voted questions from https://pollgab.com/room/brento. Here’s what we discussed: 00:00 Start 01:29 gb DBA: Hi Brent, I have over 200 threads with BROKER_RECEIVE_WAITFOR type when I execute sp_whoisactive and some of trx have high numbers on reads...
In May, Amazon brought out a new Aurora I/O Optimized Serverless instance type. By switching to it, we cut our database costs by 43% overnight, saving us about $1,200 per month.
No strings attached. Just lower prices.
So what’s the magic? Well, customers of our SQL ConstantCare® service send us diagnostic data every day for thousands of SQL Servers around the world. We import that data, process it, analyze it, and then send emails with specific actions we want the customers to take on their servers.
It’s a lot of data shuffling around, which means a lot of IO. We bring in so much new data every day, and we only keep 30 days of it online. We can’t just tune queries to cut IO: it’s legitimately new data going into the system, and that’s gotta make it to disk. (We’ve even tried cutting the data we import, too.)
When we broke out costs per day, the top cost was IO:
That’s the magic of AWS’s newest serverless price offering: it’s specifically designed for people who do a lot of IO. Amazon’s press release said it would offer “up to 40% cost savings for I/O intensive applications where I/O charges exceed 25% of the total Aurora database spend.” That’s us, alright!
If you’re using AWS Aurora, and your StorageIOUsage costs are like ours, you owe it to yourself to flip the switch over to the new instance type. Go into the portal, modify your cluster, and check out the storage configuration options:
You can switch over to I/O optimized with just mouse clicks, no cluster changes or app changes required. If you find out the cost structure doesn’t work out in your favor, you can just switch right back. (AWS does have guardrails in place to make sure you don’t flip back & forth repeatedly for busy periods.)
This new change helped us confidently run free trials for SQL ConstantCare® this month, too. Why not try it and see what you learn about your SQL Servers?
Two short yes/no questions about regional & national conferences:
I've been to an in-person event like SQL Saturday, PASS, SQL Bits before:(Required)YesNoEvents likeI intend to go to an in-person event again in the next 12 months:(Required)YesNo Δ
(If you can’t see the yes/no questions or the results, click here to view the blog post.)
I’ll close the poll after a week and write up my thoughts about the results.
You’re responsible for the health and performance of your company’s SQL Servers.
You can’t afford most monitoring products because they’re priced per-server. Your manager just can’t justify spending tens of thousands of dollars.
Besides, monitoring tools just spam you. You end up with an Outlook rule that dumps all their emails into a folder, and what’s the point of having a monitoring system at that point?
Meet SQL ConstantCare®. For one low price, we send you just one email per server per day, only when there are specific tasks you need to perform on that server.
Over 300 companies rely on it every day to monitor over 2,000 SQL Servers.
This month, we’re offering a free 30-day trial: sign up for either the $895/year plan or the $95/month plan, and we won’t charge your card for 30 days. Don’t like it? Don’t feel like you learned enough about your servers? Just cancel anytime before 30 days is up, and you won’t pay a thing.
Wanna learn more? Check out the quick start instructions and the frequently asked questions.
Let’s see what you learn about your SQL Servers!
En route from San Francisco to Juneau aboard the Ruby Princess, I stopped to hit a few of your questions from https://pollgab.com/room/brento.
Here’s what we covered:
I’m still blown away that I have a blog post here that says, “Last updated: 21 years ago.”
It’s pretty fun to see old pictures of me in the media library, too.
When I first started blogging over two decades ago, it wasn’t a business. I just did it because I enjoyed writing, sharing, and being part of an online community. It was a fun outlet, a way to contribute something in my spare time, and a method to record my life in a way that might enable me to look back later and see what I’d been doing years ago.
For 2 more days, to celebrate the 21st anniversary, I’m running a sale, too:
Fundamentals$312per year* Fundamentals Classes * Save $83
Sign upMastering$786per year* Mastering Classes * Save $209
Sign upFundamentals + Mastering$944per year* Fundamentals Classes * Mastering Classes * Save $251
Sign up
However, in the last few days, two more known issues have surfaced.
The first problem is that Cumulative Update 4 can give you incorrect query results when all of these are true:
So for example, this can give me incorrect query results:
CREATE INDEX DisplayName ON dbo.Users(DisplayName DESC);SELECT * FROM dbo.UsersWHERE DisplayName IN (N'Brent Ozar', N'Jon Skeet')ORDER BY DisplayName DESC;
To work around that problem, the CU4 documentation suggests you uninstall CU4 or enable trace flag 13166 and free the plan cache.
Update: in the comments, Paul White points out that trace flag 13166 skips a logic step when building query plans, but it only applies to descending index keys. That means the CU4 documentation might be wrong, and this bug might only apply to indexes with a descending key specified.
The second problem is memory dumps every 15 minutes if you have both Query Store and Parameter-Sensitive Plan Optimization (PSPO) turned on. Microsoft says they’re working on this issue, but for now, the workaround is to disable Query Store or PSPO, or continuously delete PSPO plans from Query Store yourself.
Should you do new installations of SQL Server 2022 today? I’m not going to give you the answer, dear reader – instead, I wanna hear your opinion in the comments. If you were deploying a mission-critical production server in June, which SQL Server version would you pick?
Is your company hiring for a database position as of May/June 2023? Do you wanna work with the kinds of people who read this blog? Let’s set up some rapid networking here.
If your company is hiring, leave a comment. The rules:
If your comment isn’t relevant or smells fishy, I’ll delete it. If you have questions about why your comment got deleted, or how to maximize the effectiveness of your comment, contact me.
Each month, I publish a new post in the Who’s Hiring category here so y’all can get the latest opportunities.
Goooood morning, party people! Today is the opening day of the annual Microsoft Build conference, an event focused on people like developers and power users who build things with Microsoft tools.
I’ve never attended Build in person before because the data part of the event tends to be fairly thin, and the releases for Azure SQL DB and SQL Server aren’t usually tied to Build’s dates. This year, it’s a hybrid event, both in-person in Seattle and online.
I’m at home in Vegas, attending virtually, and I’ll live-blog the keynote. Refresh this page starting at 9AM Pacific, noon Eastern, to see my thoughts on news as it comes out.
The first bits of news are already starting to trickle out: this morning, the SSMS release notes were updated to mention Microsoft Fabric SQL Endpoint and Fabric Data Warehouse. Yes, those would be new products, and yes, Microsoft already has something called Fabric, but this is different. If you’re bored before the keynote, you can go through this morning’s Github checkin for the Build event.
You can join me, but to do it, you’ll need a free registration for Build, so head over there now before the keynote starts at 9AM Pacific.
8:45AM: Based on this morning’s check-ins, looks like they’ll be announcing Hyperscale databases in elastic pools. Each Hyperscale elastic pool supports up to 25 databases on standard series hardware, max 100TB data in the pool. Still stuck at 100 MB/sec throughput on the log file though, and even worse, it maxes out at 130MB/sec across the entire pool.
8:49AM: From the new documentation: Fabric Data Warehouse “provides two distinct data warehousing experiences. Each Lakehouse automatically includes a SQL Endpoint to enable data engineers to access a relational layer on top of physical data in the Lakehouse, thanks to automatic schema discovery. A Synapse Data Warehouse or Fabric Warehouse provides a ‘traditional’ data warehouse and supports the full transactional T-SQL capabilities you would expect from an enterprise data warehouse. Either data warehousing experience exposes data to analysis and reporting tools using T-SQL/TDS end-point.”
8:54AM: From the documentation update list: “Optimized locking available in Hyperscale – Optimized locking is a new Database Engine capability that offers an improved locking mechanism that reduces lock memory consumption and blocking amongst concurrent transactions. This fundamentally improves concurrency and lowers lock memory. Optimized locking is now available in all DTU and vCore service tiers, including provisioned and serverless.”
8:58AM: Analysis thoughts on reading the Github checkins so far: this looks like yet another iteration of Microsoft’s data warehousing strategy that just can’t maintain focus for 3 years straight. From DATAllegro to Parallel Data Warehouse to Hadoop to Analytics Platform System to Azure SQL Data Warehouse to Azure Synapse Analytics to Big Data Clusters, there’s something broken about the leadership vision here. I feel sorry for folks who have to sell Microsoft data warehousing with a straight face: before the deployment finishes, the product’s already been “reinvented” again.
At the same time, I’m also so happy to be working in the relational database space. The language is stable, the product is stable, and I don’t have to tell clients to keep changing the way they access the database. Thank goodness for that.
9:05AM: Hmm, I thought the keynote started at 9, but they’re still running promo videos. Hmm.
9:09AM: Okay, I think this is actually supposed to be the keynote – they’re showing videos of people interacting with AI.
9:10AM: Satya Nadella took the stage and talked about his first Microsoft developer conference. He flashed back through big moments in computer history like The Mother of All Demos, the PC, client/server computing, etc. “All of this has been one continuous journey.” And a hell of a ride it’s been so far.
9:13AM: Satya called ChatGPT’s launch the Mosaic moment of this generation. I think that’s fair, but I had to chuckle – few people remember Mosaic. It was an early thing that’s long since been discarded by the wayside. If that happens to OpenAI, Microsoft is gonna be pissed about their multi-billion investment.
9:15AM: “We’re gonna have 50+ announcements, but I want to highlight 5 of them.”
9:32AM: Microsoft Fabric looks like a data lake where you have a team who governs what goes in & out, regulates the schema and security, tracks the data lineage, and curates the data model. So, uh, a data warehouse?
9:35AM: Satya’s doing human storytelling, so I’ll focus on Fabric for a second here. Fabric is a story about what happens when your data is well-controlled. That was the story of the data warehouse 20 years ago: it solved exactly the same pain points. Data warehouses fell out of favor because there was too much data, changing too quickly, and the tools changed too quickly.
Data lakes became popular because people wanted to just dump the data somewhere and figure things out later. Over time, that ran into the same problems that we used to have before data warehouses: the data wasn’t reliable, we didn’t know where it came from, the changes kept breaking reports, etc. So now, Microsoft Fabric is fixing the same problem with data lakes that data warehouses fixed with scattered relational databases.
Will it catch on? Maybe – data warehouses did – but you can fast forward and see what’s going to happen when Fabric is popular. Users will say, “I have this extra data that I need to join to my reports right now, and I don’t have the time to wait for the Microsoft Fabric admins to bring it in, so I’m just going to put it in this one place for now…”
And we’re right back where we started. Okay. If your company couldn’t fix the data warehouse’s problem, and they added data lakes, and they couldn’t fix those problems, so now they’re implementing Microsoft Fabric… I’m just gonna say maybe the problem isn’t the product you’re using.
Does that mean Fabric is a bad product? Not at all – it might be great – but it’s definitely not something I’m going to pursue.
9:40AM: Kevin Scott, CTO & EVP of AI at Microsoft, took the stage to talk about the era of the AI copilot for the next half-hour. That’s a great topic, but it’s not really my jam, so I’m going to stop the live blog here. Right now, Build’s session catalog doesn’t have any Fabric sessions, but I wouldn’t be surprised if sessions got added over the next hour or two. I’m not going to dig more deeply into there either.
Update: Optimistic Afternoon ThoughtsWhen I walked away from the computer and emptied the dishwasher (true story), I realized I wasn’t being completely fair to Fabric. There are companies who:
And for companies like that, Fabric makes a lot of sense. I don’t have a sense for how big or small that market is today, but I’m sure it’s out there – it’s the kind of thing Microsoft BI consultants would facilitate.
I think this also plays to Microsoft’s strengths: they control the cloud, the most common relational databases, the reporting tools, and the development tools. You could make an argument that Fabric stitches those pieces together in a way that Amazon and Google won’t be able to do for years, if ever. (Sure, AWS has Redshift, but that’s just a persistence layer – Microsoft is trying to argue that Fabric is a cohesive unit that brings it all together.)
Paul Turley’s a BI pro who specializes in the kind of market that Fabric services, and he has a quick summary here, plus a list of learning resources. Note the number of different tools involved in his posts and the links – Fabric isn’t just “one thing”, it’s a brand name for a whole bunch of moving parts that have been flying under different brand names over the last few years. Fabric feels like the latest brand name and vision – and that’s where I get nervous, seeing how Microsoft keeps reassembling these parts into different things.
Y’all post questions at https://pollgab.com/room/brento, and I go through the top-voted ones on my Twitch channel streams.
Here’s what we covered today:
You post questions at https://pollgab.com/room/brento and upvote the ones you’d like to see, and my job is to come up with accurate answers on the fly. Let’s see how I did.
Here’s what we discussed:
Step 1: go into Tools, Options and set this:
Step 2, set this:
Step 3, check this box:
And presto, no one will ever again question whether or not you know what you’re doing.
It’s pretty cool to look at a blog post and see, “Last updated: 21 years ago.”
It’s pretty fun to see old pictures of me in the media library, too.
When I first started blogging over two decades ago, it wasn’t a business. I just did it because I enjoyed writing, sharing, and being part of an online community. It was a fun outlet, a way to contribute something in my spare time, and a method to record my life in a way that might enable me to look back later and see what I’d been doing years ago.
For example, 20 years ago:
By 15 years ago, I had already started to focus mostly on SQL Server:
By 10 years ago, BrentOzar.com was a full-fledged business. The blog posts not only targeted tech exclusively, but the post titles were now aware of the importance of search engine optimization:
By 5 years ago, we were deeply focused on technical SQL Server issues, but also covered technologies relevant to the DBA space:
Of course I have a straight face under here, why do you ask?Over the last few years, during the pandemic and up to today, I focused a lot of the blog content on live streams & recorded videos.
I wanted to give people a community where they could see a friendly face – even when the world wasn’t open for business, and faces were covered with masks. Database work is often lonely because so many of us are the only person in our company who does what we do, and the pandemic and remote work only made those things tougher. Many of the data professionals I know are struggling with burnout and loneliness.
Those of us who’ve been lucky enough to be around other people again, whether it’s work-related stuff or friends, have started to recover. Free regional in-person events like SQL Saturdays and Data Saturdays are starting to come back to life, and big ones like SQLBits and the PASS Summit offer hope that we’ll be able to do family reunions on a more regular basis again.
However, the data community is like a river: you can never step in the same river twice. Not only have the places and ways we meet up changed, but the members of the community have changed, too. Over a decade, many of us transition to different adjacent technologies, different lines of work, or switch to management.
I took December-February off to step back and think about what I personally wanted to do next, too. Was it time for me to transition? Had I done everything there was to do in Microsoft’s relational database engine, and was it time to move on? (I certainly don’t know everything in Microsoft’s data stack altogether – the product list is huge, as is the depth of each product.)
I came to the conclusion that it was time to go back to the start, and take a fresh look at Azure SQL DB and SQL Server. The products are still widely used, and every day, more people start using them for the first time. I’m not aiming to teach new things to folks who’ve read the blog for the last 20 years – but rather, teach things in fun, friendly new ways, helping people solve database problems faster.
Wanna learn from me? This month to celebrate the 21st anniversary, I’m running a sale, too:
Fundamentals$312per year* Fundamentals Classes * Save $83
Sign upMastering$786per year* Mastering Classes * Save $209
Sign upFundamentals + Mastering$944per year* Fundamentals Classes * Mastering Classes * Save $251
Sign up
This month’s big changes are performance tuning in sp_BlitzFirst & sp_BlitzLock.
Part of the benefits of using the open source FRK is that when any of us work with really big/fast/ugly servers, we tune the FRK procs to work better in those environments – which means it’ll likely work better in yours, too. For example, this month I was working with a server doing 30k-35k queries/sec and hitting threadpool issues, and I wanted sp_BlitzFirst to return more quickly in that kind of environment, so I tuned it.
Wanna watch me use it? Take the class.To get the new version:
Consultant Toolkit ChangesI updated it to this month’s First Responder Kit, but no changes to querymanifest.json or the spreadsheet. If you’ve customized those, no changes are necessary this month: just copy your spreadsheet and querymanifest.json into the new release’s folder.
sp_Blitz Changes* Fix: exclude sp_PressureDetector from recompile checks. (#3244, thanks Edgar Walther.)
sp_BlitzFirst Changes* Enhancement: new @OutputResultSets parameter lets you return less result sets when you’re in a hurry. (#3255) * Enhancement: performance tuning for faster response time on systems with thousands of simultaneous active queries. (#3257) * Fix: no more arithmetic overflow on queries with horrific row estimates. (#3241, thanks SQLLambert.) * Fix: remove @@ROWCOUNT to avoid problems with In-Memory OLTP. (#3237) * Fix: only alert on bad cardinality estimations for queries that run > 5 seconds. (#3253)
sp_BlitzLock Changes* Enhancement: faster performance. (#3232, thanks Erik Darling.)
sp_BlitzWho Changes* Fix: no more date errors when a request’s start date is 1900. (#3243, thanks Jeff Mosu.)
sp_DatabaseRestore Changes* Enhancement: new @FileExtensionDiff parameter for folks who want to name their differential backup extensions different than Ola’s defaults. (#3234, thanks Will Spurgeon.)
For SupportWhen you have questions about how the tools work, talk with the community in the #FirstResponderKit Slack channel. Be patient: it’s staffed by volunteers with day jobs. If it’s your first time in the community Slack, get started here.
When you find a bug or want something changed, read the contributing.md file.
When you have a question about what the scripts found, first make sure you read the “More Details” URL for any warning you find. We put a lot of work into documentation, and we wouldn’t want someone to yell at you to go read the fine manual. After that, when you’ve still got questions about how something works in SQL Server, post a question at DBA.StackExchange.com and the community (that includes me!) will help. Include exact errors and any applicable screenshots, your SQL Server version number (including the build #), and the version of the tool you’re working with.
You’re planning to migrate to SQL Server 2022, and you want your databases to be faster after the migration.
This is NOT a blog post about how to migrate – that’s the same as it’s been for a long time. Go build the new 2022 servers, and use log shipping or database mirroring to sync the old and new servers. (I’m not a fan of using Distributed Availability Groups to do version upgrades. You can, but it is a heck of a lot of moving parts to set up for a one-time migration.)
This is a blog post about what to do after you migrate:
Let’s go into details on each one of those.
Stage 1: Turn on Query Store.This new-in-2016 feature is like a black box recorder for your query plans. It has a lot of weaknesses, but it’s still better than your game plan that did not include Query Store.
The goal here is to start capturing query plans as they are today, before all hell breaks loose. Compatibility levels change execution plans, and SQL Server 2022 has a lot of ways that it might change your plans. If performance gets worse, we’re going to want to see what the query plan looked like before our change.
If you’re migrating from SQL Server 2014 or earlier, you don’t have access to Query Store yet. That’s okay: you can still do the migration, but just make sure that Query Store is the first thing you turn on after you go live on 2022. (You might have heard that Query Store is on by default in 2022 – that’s misleading. It’s only on for new databases that you create from scratch – it’s not on for existing databases you migrate into 2022.)
If you’re migrating from SQL Server 2016, 2017, or 2019, turn on Query Store sooner rather than later – long before you actually do the migration project. This way, you’ve got the historical data when you’re actually running on the current version, before the database is moved to 2022.
To learn how to configure Query Store, watch this video from Erin Stellato:
In either case, once you’ve moved to 2022, don’t make any other changes to your databases at first – especially not compatibility level. SQL Server 2022 has options for compatibility level going all the way back to SQL Server 2008, so you should be able to forklift your databases over as-is, and just leave them there for a week or two.
Stage 2: After going live on 2022, gather a baseline.We want to get a crisp, clear picture of how the old compatibility level is working out for us on SQL Server 2022.
Whenever you change anything about an application – whether it’s a code deployment, new indexes, or a SQL Server version change – people are going to always say, “Hey, things are slower than they used to be.” They’re probably lying. They’re just taking advantage of the opportunity to pin the blame on you, Default Blame Acceptor, and that’s why I want you changing as few things as possible when you do the migration.
Things may actually have gotten worse for their specific query, or for the workload overall. Maybe we messed up a SQL Server configuration, missed a trace flag, or the server hardware isn’t quite what we thought it was. (True story: a recent client’s new server was accidentally provisioned on the slowest possible storage instead of the fastest, so of course their upgrade went poorly.)
Do your normal investigative troubleshooting, and you can even use Erin’s tips in her video on how to use Query Store to track down query plan changes. However, during this stage, do not upgrade the database’s Compatibility Level to fix one query’s performance. Doing so will change the performance of many other queries, some for the better and some for the worse, and you’re not prepared to troubleshoot that right now. If people are complaining about one query, troubleshoot that query. If people are complaining about the whole server, troubleshoot the whole server – but leave compat level where it is for 1-2 weeks.
Stage 3: Change databases to 2022 compat level one at a time.Compatibility level is a database-level setting. You can see it by right-clicking on a database and going into its options, or by looking at the compatibility_level column in sys.databases.
It’s database-level because it’s possible that some of your databases will perform better on newer compatibility levels – but some of them may not. That means you should take the angriest users, the ones who are the most pissed off about slow performance, and try setting just their databases over to 2022 compat level.
It’s a simple one-line change to change to , as the documentation illustrates:
SELECT GETDATE(), compatibility\_level FROM sys.databases WHERE name = 'StackOverflow';ALTER DATABASE [StackOverflow] SET compatibility\_level = 160;
“Wait – that’s two lines, not one,” you say, pointing your Cheeto-dust-encrusted finger at the screen. Well, the change is one line, but I want you to note the prior compatibility level and the date/time that you changed it – because you might need to roll back. Don’t worry, rolling back is as easy as running that same ALTER DATABASE command, but with the old compat level instead of the new one.
You can make this change whenever you want, without taking the database offline, but there’s a catch: it clears the plan cache for that database. That means you’re temporarily susceptible to parameter sniffing issues as SQL Server suddenly builds new query plans for this database.
Stage 4: Troubleshoot reports of slow performance.Most of your queries are probably going to go faster after the change. But what’s most? 90%? 99%? 99.9%? Even if just 0.1% of your queries slow down, that’s still a heck of a lot of queries to suddenly have to troubleshoot – especially when users often don’t even know which query they’re talking about. They’ll put in frantic help desk tickets that say things like “The customer screen is slow!!1!” and “The import process is down!!one!” and “My keyboard is filled with Cheetos dust!”
If tons of reports of slowness come in quickly, don’t be afraid to change the compatibility level back to the prior one. It’s a safe, quick, easy way to make the screaming stop. And… just stop there. If users are happy enough on the old compatibility level, leave it there.
However, if the slow query complaints come in at a rate that you can handle, and you’re seeing performance improvements that you wanna keep in other areas of the app, then it’s time to roll up your sleeves and do troubleshooting on the slow queries.
One of the easiest ways is to ask Query Store, “What query plans have gotten worse?” In SSMS, go into the database, Query Store, and then run the Regressed Queries report.
As soon as the report opens, you’re going to need to change the configuration, because the defaults are wrong. At the top right of the report, click Configure. Here’s what the defaults look like:
By default, it’s showing total duration – so queries that ran more often during a time window will show artificially higher on the graph – when the real root cause is that they’re running more often. Me, I like changing “Based On” to “Avg”.
Then, down at the bottom of the window, change “Minimum number of query plans” from 1 to 2. We’re looking for queries whose plan actually changed (perhaps due to the new compat level), not just slowed down. Click OK, and view the report.
The top left window lists the regressed queries, with biggest impact to smallest. As you click on each query, the top right window will update to show the performance of various plans for that query. Remember how I asked you to save the prior compatibility level, and the date/time that you changed it? We’re concerned about queries whose plan changed after the time that you changed the compatibility level. That indicates a query whose performance may have been adversely affected by the new compat level.
Stage 5: Get relief for that slow query.I’m going to list these from easiest to most time-consuming:
Option A: forcing an older plan. While you’re in Query Store’s Regressed Queries report, one of the easiest ways to get temporarily relief is to click on the query plan you used to get with the prior version of SQL Server, then click the Force Plan button on it.
I said it was easy. I didn’t say it was good.
When you force a plan, that doesn’t mean it’s going to perform well with all possible parameters for that query. This is especially true for queries that keep changing plans for valid reasons, like the example at right. That query’s got 5 different plans, and genuinely needs them. If I force a single plan for it, but my data has outlier parameters, I’m probably gonna get performance complaints from those users. We cover better ways to fix those in my Mastering Parameter Sniffing class, but that’s outside of the scope of this blog post.
Forcing a plan also means if a better option comes along later, like En Vogue’s lovin’, you’re never gonna get it. The whole reason you upgraded compat levels was to get better performance, but that query plan is stuck in the past. You can revisit them later by going into Query Store’s Queries with Forced Plans report – and I’d recommend going back in there every couple/few Cumulative Updates. Microsoft improves query plan behavior over time, so it’s possible that by unforcing a plan later, you’ll get better plan options.
Besides, you’re going to want to go into that Queries with Forced Plans report anyway because query plan forcing can fail, plus the query may change over time. As the query changes, the forced plan will no longer be relevant (because it’s for a query that no longer exists.)
I do still like forcing plans – and I wanna tell you about it because it’s quick and easy – it just has drawbacks, so we need to keep going with more options.
Option 2: give SQL Server hints for the plan. Let’s say that in SQL Server 2022 compat level, SQL Server decided to do something in the query plan that made performance worse instead of better. Let’s say it used batch mode processing on a rowstore index. If you want to disable that behavior, you can add a hint to the query without touching the query itself.
This is called Query Store hints, and David Pless has a tutorial on it. It’s not as easy as forcing a plan in the Query Store GUI. You’re going to have to get the query’s ID from Query Store, then apply the query hint you want using sp_query_store_set_hints.
It does involve work on your part, but … it’s still faster than fixing/tuning the query. As soon as you start changing the query itself, you’re probably dealing with getting approval from different folks in the company, getting it into source control, testing it, getting it deployed, etc. Query Store hints are instant, presto, in production. I can’t imagine how that could go wrong.
Option III: fix/tune the query. This takes the most work, but I’ll be honest: as a consultant, it’s the one I do the most often. Usually when I pop open a query that people are complaining about, I say, “Okay, here’s why it’s not performing well, and if I change this, this, and this about the query, it’ll go dramatically faster than it used to, and it’ll get a better plan overall.”
This is the option that doesn’t just bring performance back to its prior levels – it makes performance better, and after all, isn’t that what we want? (Honestly: no, a lot of people just want it back to the way it was, and that’s why this is the last option.)
SUM(MARY)When you’re preparing to migrate to SQL Server 2022:
But while you’re in those final two stages, pay particular attention to the bold words. If users aren’t complaining about performance, move on to the servers where they are complaining about performance. Every server has queries that need to be tuned, and you need to focus on the ones that users will actually appreciate.
This one’s broken up into two parts because I took a bio break mid-stream:
And part two:
I’m doing live streaming on my Twitch channel on Wednesdays & Thursdays this summer, and the recordings will go live later on my YouTube channel. I’ll take questions from PollGab, do live coding, work on the First Responder Kit, and write blog posts.
The stream will start at 8:15AM Pacific, 11:15AM Eastern, and you can see the time in your local time zone here. I’ll stream for around 45 minutes.
To get notifications when I go live, follow me on Twitch, or Google Calendar users can use this invite. See you in Twitch!
If you right-click on a database in SQL Server Management Studio, you get an option to set Compatibility Level at the database level:
When you upgrade SQL Server or you want to improve performance, which option should you choose? Should you always go with the newest compatibility level? If you have an old application, can you upgrade SQL Server but still keep the old compat level without the vendor knowing? Let’s hit the common questions.
What does compatibility level do?When Microsoft brings out a new version of SQL Server, they tend to keep the newest features only available in the newest compatibility levels. For example, SQL Server 2022’s Parameter Sensitive Plan Optimization (PSPO) is only available in databases running under SQL Server 2022 compatibility level.
That means if you’re taking a database that used to live on an older SQL Server, and you want to host it in SQL Server 2022, and you want it to have the same behavior that it’s always been used to, you should keep it on the compatibility level that it’s currently on. For example, if you’re hosting it in SQL Server 2016, and the database is currently at SQL Server 2016 compatibility level, then you could move the database to a 2022 server, but keep compat level on 2016, and the users shouldn’t notice the difference.
In reality, though, there are things inside SQL Server itself, at the server level, that will change no matter what your compatibility level is. For example, if Microsoft deprecates a feature and removes it altogether, that feature isn’t available even if you’re on older compat levels. (Hello, Big Data Clusters.)
Should I change compatibility level?If there’s a specific feature that you need that’s only available in some compat levels, then yes.
However, if you’re happy with performance, then no. Hear me out: changing your compatibility level can make performance worse instead of better. Sure, in some cases, it makes performance GREAT – but because all change = risk, then changing compat level when you’re already happy is dangerous.
How does compatibility level affect performance?If you migrated from SQL Server 2019 to 2022, here are ways that changing a database’s compatibility level can make things better or worse:
In each version of SQL Server, different features are enabled under newer compatibility levels. Before changing your compat level, you should review what features are added when you change from your current compatibility level to the new one. This is a good starting point.
What should I measure before changing compatibility level?In theory, you should have a performance baseline of things like:
Then, when people suddenly complain about performance, you can check your baseline to see whether things actually got worse, or whether your users had taken up eating shrooms. You could also track down which queries were NOW at the top of your resource-consuming query list, look at what their query plans USED to look like, and then figure out how to get back to the good old days.
Common ways to accomplish this are third party monitoring products, Query Store, or the First Responder Kit.
In reality, you’re not gonna do any of this ahead of time. So, when you change compatibility levels on the fly, and performance gets worse, you’re not going to have any answers.
Does that mean I shouldn’t touch compatibility level?No, not at all! You can change compatibility levels whenever you want, one database at a time. You can also change back instantly as well. You just need to be aware of when you made the change, what you changed, and communicate it to the rest of the team so they can roll your change back if necessary.
What compatibility levels are available?The screenshot at the top of the blog post was taken in SQL Server 2022, and even in this recent release, Microsoft supports compatibility levels going all the way back to SQL Server 2008. That’s kinda awesome, because it means that Microsoft is trying to keep old databases working great in newer versions of SQL Server.
In theory, that means you can take an old vendor application that was once certified on SQL Server 2008, and keep moving it to newer and newer versions of SQL Server. In theory, that means it’ll keep working the exact same way as long as you keep the same compatibility level – and hey, it might even get faster if you change to newer compatibility levels.
So, can I actually do that?Well, no. I mean you could, but you might get caught.
Here’s the thing: the vendor might be relying on a feature that’s no longer available in newer versions of SQL Server. I gotta be honest, that’s extremely unlikely, but it is possible. And if they are, and their application suddenly breaks, you can’t restore a newer SQL Server database to an older version of SQL Server.
So if you take your SQL Server 2008 server, back up the databases, restore them onto SQL Server 2022, and then start using the app – and people start complaining – you can’t restore those 2022 backups down onto SQL Server 2008, even if they’re still in the same 2008 compat level. You can only restore to newer versions of SQL Server, not older.
Therefore, you’re taking a risk when you move databases onto newer versions of SQL Server. Make sure the vendor actually supports the newer version of SQL Server, because you don’t wanna be the person that the vendor blames for their application not working successfully.
Want to watch me write this blog post?I streamed this blog post live if you want to get a rough idea of what’s involved with writing a post like this:
HONK HONK! It’s time for a fast round of answers to concise questions y’all posted and upvoted at https://pollgab.com/room/brento.
Here’s what we covered:
Some of the questions y’all post at https://pollgab.com/room/brento have easy one-line answers. Let’s knock ’em out:
George: Hi Brent, what recently has been the most challenging/surprising/new-to-you performance issue you have encountered?
SQL Server 2019’s slowdowns. I spent days working on that.
RoJo: Have you used Distributed AG as a way to upgrade major versions of SQL server without downtime? Seems like a nice way to try it out on a second site before a switch. Any concerns? Maybe jump from 2016 to 2019, or 2022 to big a jump? Cheers
No because it’s so much work to set up.
ConsultantWannabe: I’m a generalist trying to make the jump into the consultant role, I don’t want to be “a jack of all trades”. How should I start finding that niche (or that “expensive” thing to stand next to, apart from SS)? Do you think just asking around to the guys in suits is a good idea?
Ask executives what technology problem they can’t solve with their current staff.
Ive_Got_Heaps: Hey Brent, Our DB is loaded with heaps as our ERP system doesn’t utilize primary keys (begins crying). My plan is to create clustered indexes on existing columns where possible, or create an Id column for tables where no existing column can be used. Is this a sound approach?
Ask the ERP vendor. If it’s an in-house app, watch this.
Yevgeny: What are the top causes of data file corruption for SQL Server on a Windows cloud VM and how do you avoid them?
In the cloud, you don’t get root cause analysis from your vendor. Do backups and high availability (like AGs for automatic page repair.)
SQLrage: In 2019, can statistics updates on a table cause an execution plan to be recreated for a parameterized proc that hits the table but does not use that updated statistic in particular? Trying to better understand why plans regenerate automatically.
Read this and do a test.
Bart: Is there any harm in deleting Extended Properties of a table column? I inherited a database that’s been converted from MS Access into MS SQL several years ago and I think the extended properties are a result of that conversion.
What’s the benefit in deleting them? Why risk it? Who cares?
My latest toy, heading out for engine work firstPiotr: What file system folder convention do you like to use when locating data files / log files for a new SQL DB?
\MSSQL\DATA
Chetan: Which nice car did you buy recently? What do you drive now?
A 1964 Porsche 356 coupe, which is getting its engine checked out first before I take it on any road trips. Until it’s done, and because it’s springtime, I mostly drive my Speedster replica.
MacAries: I had an On Prem 3 CTE then join for result query that ran subsecond, but coming from Azure Function that sent multiple and crippled the on-prem server to a 20 result /minute nevermind the lock and batch waits is their some basic translation that azure needs to get the query run?
Read this or watch this.
Will Marshall: Do you run into any common performance issues with SQL always encrypted?
I’ve never had a client use it.
Haydar: What is the best way to copy a few tables from SQL Server to PostgreSQL?
I would ask this guy.
CB: Hi Brent – It seems SQL functionality isn’t supported in SQL task editor. Statement: Declare @sql nVARCHAR(max) Error: The Declare SQL construct or statement is not supported. Is there a solution to that?
I don’t know what the “task editor” is.
Will Marshall: What are the best courses / books for learning SQL Always ON?
I haven’t seen any that were updated with what’s new in SQL Server 2019, 2022, or Managed Instances Link.
Bocephus: For network perf testing between two windows nodes, what tools do you like to use?
Copy a large file like a backup.
Hal Jordan: What should we look at when OS pages per second paging rate is high for the bare metal SQL Server 2019 instance?
Attend this class.
Mirza: Discussion happening in the company about automating SQL patching on clusters using Powershell and SCCM. Both PS script and SCCM are owned by the server team. Does the DBA team lose control and does it matter? What is your experience/opinion regarding automating SQL patching?
Read this.
Kyle: Hi Brent! What are the best practices for restarting your SQL service on an HA system? Is there any way to do it with causing any downtime?
No, all restarts will cause downtime, even for cluster and AG failovers, so for minimal downtime, use clusters and AGs.
SQL_Developer_Admin: If there is left outer join, then why there is right outer join as well, if we could just swap the sides of the tables. Any scenario you know where only left join can be used or right join can be used.
Sometimes it’s nice to have multiple tools to approach the same problem from different angles.
Eyvindur: You mentioned caching as a possible solution to lessen the load on SQL Server. Are triggers are good solution for cache invalidation?
The idea of caching is to lessen load. Do triggers add or lessen load on the database?
Eduardo: Do you see any RDBMS disruptors threatening to steal Microsoft / Oracle market share in the near future?
If by “near” future you mean 5-10 years, yes, Postgres and AWS Aurora.
ChopstickWizard: Been mulling this over for sometime. There are instances where I want to recommend a product to a team, like example : Cockroach db, mage.ai etc. But the problem is, they seem “non enterprisey” just by their name in case of “cockroach” or mage.ai’s tag line. Have you faced this?
You mean names like ChopstickWizard?
Post your questions at https://pollgab.com/room/brento and upvote the ones you’d like to see me answer.
Here’s what we discussed today:
Ever wonder how fast people are adopting new versions of SQL Server, or what’s “normal” out there for SQL Server adoption rates? Let’s find out in the spring 2023 version of our SQL ConstantCare® population report.
Out of 3,002 monitored servers, here’s the version adoption rate:
The big 3 versions are all within 1% of the last quarter’s numbers:
On the other extreme:
Just 13% of the population are running unsupported major versions (2014 & prior).
Here’s how adoption is trending over time, with most recent data at the right:
In other news, we do actually have 6 folks now running SQL Server on Linux! It’s a total of 32 SQL Servers, a mix of 2017 and 2019, running on a mix of Ubuntu and Amazon Linux. That’s about a 1% adoption rate. Only 4 of those are Dev or Express Editions, too. I’m still feeling pretty comfortable with my 2018 prediction that in 2026, <5% of SQL Servers would be running on Linux.
Other interesting tidbits:
34% of servers are fully patched to the latest possible Cumulative Update. That’s awesome! Nice work, y’all. My first thought was, “Oh, I bet the old versions like 2012 are fully patched, but the new ones aren’t patched because the patches keep coming out.” Nope, it’s actually the opposite: the most-currently-patched folks are on 2016 & 2017. The least-patched are the unsupported versions that haven’t had patches in forever. Disappointing.
Only 7% of SQL Servers are on unsupported versions or builds. That’s awesome too! Keep up the good work on patching, y’all.
It’s time for summer school!
I’m doing live streaming on my Twitch channel on Wednesdays & Thursdays this summer, and the recordings will go live later on my YouTube channel. I’ll take questions from PollGab, do live coding, work on the First Responder Kit, and write blog posts.
The stream will start at 8:15AM Pacific, 11:15AM Eastern, and you can see the time in your local time zone here. I’ll stream for around 45 minutes.
To get notifications when I go live, follow me on Twitch, or Google Calendar users can use this invite. See you in Twitch!
You, dear reader, are most likely a Microsoft SQL Server user – either a DBA or developer.
Set your pencil down for a second because you’re not going to learn about a Microsoft product today, nor are you going to learn something that is going to be immediately useful to you in your job. Today, I’m writing about a completely different product just to give you a better insight on what else is out there.
Our SQL Server monitoring app, SQL ConstantCare®, uses AWS RDS Aurora PostgreSQL as its database back end. I wrote about that design decision five years ago, and since then, we’ve been really happy with both PostgreSQL in general and AWS Aurora in particular. We just didn’t have to spend time worrying about the database layer – for the most part, it just worked, in the same way Microsoft Azure SQL DB just works.
No matter what database platform you use, costs are split into two main parts:
Traditionally, us data people have wanted the fastest storage we could get, and the most compute power we could get. That led to problems with management because we bought really good stuff, and then… it usually sat around idle, unused. We had to pay a lot to handle the peaks of our workloads, and then we were locked into expensive stuff that rarely got hit hard.
Serverless can reduce those costs while simultaneously handling more bursts.To help reduce costs and improve flexibility, several years ago, AWS introduced Aurora Serverless. Instead of paying for fixed server sizes (like 8 cores and 60GB RAM), Aurora Serverless:
Not minutes. Not seconds. Milliseconds. That’s bananapants. You just define a minimum and maximum size for your database servers, and Aurora automatically handles the rest, independently, for each replica. Instead of provisioning an 8-core, 60GB RAM server, you could say you want your config to swing anywhere from 2 cores and 4GB RAM all the way up to 64 cores and 128GB RAM. (More on the exact provisioning later.)
Somebody at Microsoft is going to be tempted to pipe up, “Yeah, we have serverless in Azure SQL DB too,” and I can’t help but respond in meme form:
Yes, that’s actually in the documentation, and it’s why you’re not hearing a lot of buzz about serverless here in the Microsoft & Azure communities. Azure balances load in minutes, not milliseconds, and drops connections at the worst possible time – when you’re under heavy workloads and the server isn’t able to keep up.
I suppose that’s serverless, in the sense that you lose the database server temporarily? Let’s move on.
Aurora Serverless was a perfect fit for SQL ConstantCare®.SQL ConstantCare® consists of a small app that clients install and point at their SQL Servers. Once a day, that app polls all of their SQL Servers, exports diagnostic data to JSON files, encrypts it, and sends it to us. We can’t predict when it’s going to happen – it’s based on the schedule that users set up on their end, which tends to be even-hours (like noon or 4PM local time in their own time zone.)
When the files come up to us in AWS, we:
That application logic layer has been serverless (AWS Lambda) all along, and I wrote about that design decision as well. That’s paid off really well as we scaled to thousands of client servers because the workload is really bursty. We don’t wanna pay for lots of app servers to sit around idle most of the time, but when client files come in, we want to process the data as quickly as practical.
The problem right from the start? The database layer! AWS Lambda would see lots of incoming files all at once, spin up lots of workers to process all those files, and then – wham, all those workers would try to tackle the database server at the same time. Richie had to work hard to smooth out the peaks, or else we just kept DDoS’ing our own database server.
By switching to Aurora Serverless, the database could now better handle bursts of incoming files – while simultaneously cutting costs by downsizing capacity for the hours per day that we sat mostly idle.
When you create serverless replicas, you size them in Aurora Compute Units (ACUs). One ACU = 1 core and 2GB RAM. Because we wanted to make sure it worked, we picked:
And then sat back and watched the replica automatically handle workloads.
Aurora resizes our database servers FREQUENTLY.Here’s an average of the primary writer’s ACUs over a 24-hour span, in 1-minute intervals:
Don’t worry about the time zone or peak hours – just think about this being a 24-hour time span. On average, we have a few different bursts per day, but we generally range from around 2-3 ACUs (cores) up to around 28 ACUs (cores). But here’s where it starts to get wild – instead of looking at averages, let’s look at the MAX in each minute range:
Aurora is frequently slamming the gas pedal, taking us right up to 32 cores! We’re maxing out at 32 cores all the time throughout the day. And conversely, the min in each minute:
It’s slamming the brakes right down to 1-2 ACUs all the time.
Lemme rephrase those 3 charts: in many one-minute time spans of the day, our Aurora database server scales up and down automatically from 1 core to 32. All day long. In fact, it’s rare to have a 15-minute time span where Aurora didn’t ramp capacity up and down like crazy. Here’s the same set of charts in 15-minute intervals – first, maxes:
In most of the day, in any given 15-minute time span, we were hitting 15-32 cores. But for mins:
It dropped right down to 1-2 cores.
Again, this is night-and-day different to what Azure is doing. Microsoft’s all, “Hey, let’s think about this for a while and transition up and down over the span of a minute or two.” Amazon’s reacting with lightning speed, so as each query comes in, it gets the power it needs and our apps don’t notice the difference.
Admins have to ask new questions.Because Aurora’s reaction time is so fast, it opens up totally new questions for performance tuning and cost management.
“Are we doing the right database maintenance?” Resource-intensive queries literally cost you money. We were spending around $50/day for storage throughput at one point, and one day’s cost suddenly shot to $250. After digging into AWS’s Performance Insights (which is fantastic, by the way), we determined that a surprise automatic index maintenance job had cost us $200. That experience changed the way we think about index maintenance, and for SQL Server admins, that would be especially eye-opening. Are you sure you need those index rebuilds? Can you prove that the server is actually getting faster for your money? Odds are, it’s not.
“Should we raise or lower the max ACUs?” If we raise it, then resource-intensive queries might be able to finish more quickly – but scaling up compute power doesn’t always result in a linear speed-up of queries. Just because you go from 16 cores to 32 cores doesn’t mean big queries finish exactly 2x faster. The question morphs into, “How low can we go? How low can we set the max without hurting our most valuable queries?”
“When we change ACUs, how does that affect IOPs?” When you add more ACUs, you get more memory available to cache data, which means you hit storage less frequently. When you reduce ACUs, you’re likely going to hit storage more. There are no dials to set for storage performance – Amazon manages that for you automatically, and just bills you for every IO you do. There’s no easy across-the-board answer here, either – the answer is going to depend on your own workloads. For SQL ConstantCare®, we’re constantly importing new data and querying it, so cache is less useful to us.
“Does serverless make sense for our workloads?” Does this scale up/down, cost up/down actually benefit your application either in terms of lower cost, or faster performance? For our particular workload, it was really funny when we first switched over – Aurora Serverless was almost exactly the same cost as our previous pre-sized, inflexible servers, hahaha! However, by tuning our max ACUs, we could more easily find a sweet spot where we could reduce costs without harming performance.
But here’s my favorite part of Aurora Serverless: it gives us the ability to run new product experiments without worrying about capacity. For example, since the very start, I’ve always wanted to have a completely free tier of SQL ConstantCare that alerts you about the most vital issues, like missing backups, database corruption, or an urgent patch. Before moving to Aurora Serverless, I didn’t want to test that out because a huge wave of free adoption might cause us database performance nightmares. Now, the database server isn’t holding us back from those issues. (So stay tuned for some fun experiments!)
“Does migrating from SQL Server to Postgres make sense?” By this point of the post, you might be jealous of this cool technology. Remember, though, that right at the beginning I told you that today’s learning wasn’t going to be too relevant to your current job. You don’t really wanna change an entire application’s back end from one database platform to another, regardless of how easy Babelfish might make it seem. Back end migrations rarely make sense. However, I just wanted to talk about this technology today so you could see what’s going on in other database platforms.
Besides, Microsoft’s surely working on similar capabilities for Azure SQL DB. I love how cloud databases turned into an arms race because they’re a real differentiator for cloud vendors.
If you liked this post, you’ll probably enjoy Ed Huang’s musings on building a database in the 2020s. He asks, “If we were to redesign a new database today from the ground up, what would the architecture look like?” To be frank, it wouldn’t look like Microsoft SQL Server, and between his post and mine, you’ll understand why. I’m not saying SQL Server is dead, by any means – but if you’re a developer building new-from-scratch applications in the 2020s, SQL Server probably isn’t your first choice, and that’s why.
It was a dark and stormy morning, on the last day of my Panama Canal cruise. Before I perished, I sat down to answer your top-voted questions from https://pollgab.com/room/brento.
Here’s what we covered:
- 00:00 Start
- 00:36 ClippyTheDBA: Hi Brent. How you keep up-to-date with technology in general and in SQL Server specifically?
- 02:56 Björk: Should foreign keys be indexed from day1 or only when performance issues arise from lack of a NC index on the foreign key?
- 03:55 ClippyTheDBA: Hola Brent. If you just started your consulting business, what thing(s) you would change or do differently (based on your current consulting business experience)? [Kind of Lessons Learned]
- 06:00 Haydar: What bad stuff can we look forward to once SQL Server hits the max size for a data file? Any interesting stories of this happening in the wild?
- 06:52 Eduardo: How do I find the worst performing query for a given app using the first responder kit?
- 08:51 Netanel: Is it safe to overlap invocations of the tools in the first responder kit (i.e run sp_blitzindex at the same time of running sp_blitzcache, sp_blitzfirst etc)?
- 09:48 Alex: Hi guru, Regarding your last office hours where you talked about 2022 version. I was planing to upgrade from 16 to 22, now you got me thinking. Should I upgrade to 19 instead as 22 is full of bugs?
- 10:56 Gopher: What criteria do you use when picking cruise ship line A vs B?
- 13:09 AGAnyday: I am in a shop with backup policies dictating full backups daily, Colleague DBAs say there was a research policy informing that course of action. What is the best gentle approach to convince the management to resort to full backups for weekends then do differentials every night?
Is your company hiring for a database position as of April 2023? Do you wanna work with the kinds of people who read this blog? Let’s set up some rapid networking here.
If your company is hiring, leave a comment. The rules:
If your comment isn’t relevant or smells fishy, I’ll delete it. If you have questions about why your comment got deleted, or how to maximize the effectiveness of your comment, contact me.
Each month, I publish a new post in the Who’s Hiring category here so y’all can get the latest opportunities.
Off the coast of Costa Rica, I went through your top-voted questions from https://pollgab.com/room/brento.
Here’s what we covered:
I’m running into something that I’m having a hard time believing.
A client was hitting CPU issues during load testing, and they swore all things were equal between their SQL Server 2016 and 2019 environments. The 2019 box was having CPU pressure issues that didn’t show up on the 2016 box. I’ve played this game before, and every time, the root cause has been different configurations between the two servers.
However, this time, not only were the servers the same, but I’m even seeing this same behavior with a simple query that I can reproduce on any 2016 vs 2019 setup. I haven’t tested on any other versions yet, but after a day of banging my head against the wall, I figured it was time to bring in the smart people – and that means you, dear reader.
Take any two identical servers, and I do mean identical – same CPU speeds, same power savings settings – and run this setup script. We’re creating a database in 2016 compat level just to compare the exact thing across all versions:
CREATE DATABASE TestCompat2016;GOUSE TestCompat2016;ALTER DATABASE CURRENT SET COMPATIBILITY\_LEVEL = 130;DROP TABLE IF EXISTS #Numbers;GOCREATE TABLE #Numbers (Number INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,TestString VARCHAR(100));GOINSERT INTO #Numbers (TestString)SELECT TOP 5000000 'Hi'FROM sys.all\_columns ac1CROSS JOIN sys.all\_columns ac2CROSS JOIN sys.all\_columns ac3;GO
Then turn on statistics time, and run this query:
SET STATISTICS TIME ON;GOSELECT TOP 1 UPPER(LOWER(LTRIM(RTRIM(CAST(Number AS NVARCHAR(100)))))), SUM(1) AS recsFROM #NumbersGROUP BY UPPER(LOWER(LTRIM(RTRIM(CAST(Number AS NVARCHAR(100))))))ORDER BY SUM(1) DESC, UPPER(LOWER(LTRIM(RTRIM(CAST(Number AS NVARCHAR(100))))))OPTION (MAXDOP 1);GO 3
The query’s terrible, of course, but it’s designed to do a fixed amount of CPU work every time. We’re not disk-bottlenecked – the tiny numbers table fits easily in memory. You’re going to be tempted to change the table design or query design, and you’re absolutely welcome to, but make sure the query is CPU-bottlenecked, not read-bottlenecked.
Compare the CPU time (not duration) across SQL Server versions. Because I’m paranoid, I built a brand new Windows Server 2016 box from scratch up in the cloud, and installed two instances of SQL Server on it. Left hand window is SQL Server 2016, right hand window is 2019 RTM – don’t run them at the same time, obviously, because that would screw up the CPU availability:
SQL Server 2019 uses 5-10% more CPU time to execute the same query.It’s not just single-threaded queries, either – if I let the query go parallel by removing the MAXDOP 1 hint, 2019 is still slower:
You’re also going to be tempted to say, “Just change the compat level, query, or indexes to make the whole thing go faster on 2019” – but that’s not the point, because often we can’t tune an entire running workload. (In this demo case, 2019 compat level actually works beautifully, dropping the CPU time down by about 1/3, and I wish the client’s case was that easy. They already tried that before they called me. Bummer.)
You’re also going to be tempted to say, “I bet it’s fixed in a 2019 Cumulative Update,” in which case, check out this wider screenshot. The far right window is 2019 CU19, the most current one, and it exhibits the same higher CPU usage as 2019 RTM:
You might even be tempted to say it’s the new lightweight query profiling – try turning that off:
ALTER DATABASE SCOPED CONFIGURATION SET LIGHTWEIGHT\_QUERY\_PROFILING = OFF;
And at least in my tests, it makes no difference.
That’s where you come in.If you have access to absolutely identical environments (or different versions installed on the same base hardware), are you able to replicate these findings? Does the same query use more CPU time on 2019 than it did on 2016? The best evidence for this is a side-by-side screenshot of the same query’s output across the different versions.
For our own evil purposes, 2017 doesn’t really matter (because you’ve gotta get to current versions anyway), but if you want to test on, say, 2016 vs 2022, you’re welcome to. In our brief testing, we’ve seen 2022 exhibit the same CPU problems as 2019.
I wouldn’t use this case as evidence that 2019/2022 are “bad” by any means – they’re fine. It’s just helpful for folks to understand, when they’re doing capacity planning for new versions, that they may have to buy more licensing for the same server at upgrade time. In this particular client’s case, we’re probably going to have to bump from 8 cores to 10 cores in order to handle the same workloads – in their cases, the CPU difference is closer to 20%.
You’ve been working hard all month to get through our newest class, Fundamentals of PowerShell for DBAs. This week, it all starts to come together as you put together PowerShell plus SQL Server to automate tasks on a regular basis:
If you couldn’t keep up, no worries – you can always purchase the course, or pick up my Recorded Class Season Pass Fundamentals to revisit the material when your schedule is less hectic. Hope you enjoy the free training!
Sometimes, y’all post questions at https://pollgab.com/room/brento and they don’t get a lot of upvotes, and reading through ’em, I bet it’s because other people don’t quite understand what you’re asking. I think there might actually be a good question at the heart of it, but … I’m just not sure what it is, and it needs to be rephrased.
Here’s a rundown of some of ’em that came in recently. If you recognize one of these as yours, you’re welcome to re-submit it at PollGab, but with clarification.
MonkeySQLDBA: Recently watched one of your video on fragmentation, great stuff. quick question, if your fill factor is set to 80, will the internal fragmentation get to 70% faster than leaving fill factor to 0?
Zoom out: what problem are you trying to solve? What’s the action that you would take based on this knowledge? Think about those, and then re-post the question.
Piotr: If SQL Server and PostgreSQL were aircraft which aircraft models would they be? F14 vs SU57?
I love the question, but I don’t know enough about aircraft to answer it. I don’t even know if those two airplanes are good, bad, or the same, hahaha.
Keith: Hey Brent! Is it safe to upgrade my Azure SQL database from compatibility level 140 to 160 just to be able to run the GENERATE_SERIES function?
Is it hard for you to create a numbers table? Like, really? I’m not being sarcastic, but if you need a list of numbers, why wouldn’t you just create one?
I Cannot Do This Alone: I’m sure it has always been hard to get skilled help, BUT… have you ever heard of “non-tech” companies sharing there tech talent in like a pool? I know I’m on the hook for a trashing here, but better by you than my CIO 😉
I think you’re describing outsourcing – having a group of full time tech employees that you can call on whenever you need them, and just pay for what you use.
“Can you show us your feet?” Wat?!?Henry: I am planning on an in place SQL Server upgrade from SQL Server 2012 to 2016 on a Windows Failover Cluster. All of my databases are in Full mode with Transaction logs taken every 5min. Should i put the databases in Simple during the upgrade?
Don’t upgrade in place, period.
Government Cheese: How do you like to measure IOPs for SQL Server storage (bare metal and cloud VM)?
First, I don’t, but even if I did – are you talking about measuring how many they already consume, or how much a new server provides? If you’re asking how many they already consume, how is that useful? You don’t know if the users are happy or not, or if you need to reduce storage throughput or increase it.
ConsultantWannabe: Hey Brent, you teach we should stand next to expensive things (like SS or Oracle) as contractors/consultants, any advice to identify our own expensive thing to stand next to? Obviously apart from standing nex to SS. Thanks
Are you … asking me … how to find out how much things cost? I’m confused. Why wouldn’t you just … ask management what the most expensive thing in the shop is?
Jeremiah Daigle: I have a server that has 2 8Core CPUs, and only have 8 enterprise core licenses. I was planning to just remove one of the CPUs, but ran into issues not having the blank to put back in. Is there anything to be concerned about by turning down each CPU to 4 cores in bios instead?
Having a blank to put in? I’m not sure what you mean – you shouldn’t need a “blank” CPU. I think someone’s pulling your leg, like they’re telling you to go get blinker fluid.
Maksimilian: What’s the best technique for a SQL sproc to self audit the params it was called with?
Self audit? Have the proc log them to a table. (I think I might be misunderstanding the question because it seems so obvious, but if you want to log something, and you’re in a database, well, uh, put it in a table.)
Eduardo: Linked-in provides automated public notification of completed course training. How should DBAs notify potential employers of completed Ozar training?
You mean … how do you add things to your resume in LinkedIn? I’m genuinely confused – are you asking how to edit your resume? I’m guessing you just click Edit on your profile, right? Put in whatever text you want there. If you’re saying that you have a problem because you need automation every time you complete any of my courses, and I have so doggone many of them, then stop putting each one – just put Fundamentals and Mastering.
Hangman: When is sp_whoisactive context_switches a useful metric for performance troubleshooting?
Ask whoever told you to look at that metric. Otherwise, don’t walk into the airplane cockpit, point at a gauge, and ask the pilot, “Hey, what’s that dial mean?” That’s not an effective use of anyone’s time. SQL Server is way worse than an airplane cockpit: there are precisely 1.21 gigawatts of metrics out there, and most of ’em just aren’t useful.
Eduardo: Is it good idea to start identity integer cols for new fast growing tables at the max negative value for a big int? Do you see this much in the field?
It’s fine. I almost never see it.
Tony: Will you be purchasing TSQL fundamentals 4th edition?
No. I’m sure it’s good, and I’m sure you doubt my T-SQL abilities, fair enough, but I’ve moved on to learning other stuff.
Isaac: How do you find all the queries that are using the kitchen sync query pattern (Col1 = @Col1Val or Col1 IS NULL) AND (Col2 = @Col2Val or Col2 IS NULL)? How do you find the worst of the worst?
Instead of looking for anti-patterns, ask, “What are the 10 worst-performing queries that I need to tune, and what are the anti-patterns in those?” That’s what sp_BlitzCache does.
TheCuriousOne: Hi Brent! From your perspective, is there any open problem/issue preventing a problem free upgrade from SQL Server 2019 to 2022 and if so, what are the gotchas to look out for?
Microsoft used to publish detailed upgrade guides for each version of SQL Server, but they stopped doing it. Check out the most recent one from 2014 (PDF) and that’ll give you a rough idea of how complex it is to migrate an existing environment.
Eduardo: What is your favorite graph database and why?
I don’t use any myself, so I’m not qualified to answer that.
Wasn’t_Me: We are thinking about switching from Azure to AWS. On docs.aws.amazon.com I find this phrase: “When you set up an Amazon RDS DB instance for Microsoft SQL Server, the software license is included.” What?? Does it means that on AWS I don’t have to pay SSRS, SSIS, SSAS?
Licensing is included in the hourly rate, yes. Amazon also offers bring-your-own-licensing. Keep in mind that you said RDS, and RDS doesn’t have SSRS, SSIS, and SSAS – you’ve got a lot more reading to do. Fortunately, I’ve got a training class to help.
Marian: Hi Brent! Have you even been to Romania? Would you consider attending some big tech event in Romania in the nearby future?
No, and since the pandemic, I’ve cut back a lot on my conference schedule. I’m sure Romania is nice, but I did a quick Google search and didn’t see any SQL Server conferences in Romania. I’m not really interested in non-SQL-Server conferences – when I want to learn other technologies, I tend to use cheaper/easier methods rather than traveling.
Neil: I set up all my SQL servers with TCP/IP enabled only. A developer is trying to connect with named pipes. Should I enable named pipes or force them to use TCP/IP?
I don’t have any opinion on this one whatsoever. (I don’t think I’ve ever disabled named pipes.) Why did you disable it?
Before heading out to Old Town for sightseeing, I went through your top-voted questions from https://pollgab.com/room/brento.
Here’s what we covered:
You’re over the hump! Just two weeks left to go.
All month long, I’m giving away our newest class, Fundamentals of PowerShell for DBAs. Here’s what you’ll be learning this week:
In case you didn’t know before now, go head over to the class and watching the Before the Class modules that explain how to set up your workstation to follow along. Download these resources files to help, (The slide decks are also available to paid students.)
Not patient? Can’t wait for the rest of the videos to go live? Recorded Class Season Pass Fundamentals holders can jump in now – it’s included with your existing membership. Enjoy!
When In-Memory OLTP came out, I worked with it briefly, and I remember coming away thinking, “Who in their right mind would actually use this?” I was so horrified that I wrote a presentation about it and gave it at a couple of conferences:
Many laughs were had, but obviously that didn’t make me any friends at Microsoft, ha ha, ho ho. I figured I was done with Hekaton, and I wouldn’t ever have to see it again because y’all all saw the video, and you’d never be crazy enough to implement that feature.
Well, here it is in 2023, and recently I’ve talked to a couple of architects who wish they could go back in time and watch that video. In both cases, they suffered from the same issue.
The short story is that the more data you put into durable In-Memory OLTP tables – and even just 5GB of data can hit this issue – the more your startups, failovers, and restores turn into long stories, to the point where other databases on your SQL Server are practically unusable.
Setting up the 5GB Votes tableTo demonstrate the problem, we’ll start with a large copy of the Stack Overflow database. I’ll drop my nonclustered indexes just to make the next screenshot more clear, and then I’ll list the tables in the database to find a good candidate for the demo.
The highlighted table, dbo.Votes, has about 150 million rows and takes up about 5GB of space. That’s not big data by any means, and we can easily fit it in memory on our 8-core, 60GB RAM server. Let’s migrate it into In-Memory OLTP in order to make performance go faster:
ALTER DATABASE StackOverflow SET COMPATIBILITY\_LEVEL = 130, /* Or you can go higher, too */ MEMORY\_OPTIMIZED\_ELEVATE\_TO\_SNAPSHOT = ON;GOALTER DATABASE StackOverflow ADD FILEGROUP StackOverflow\_ram\_fg CONTAINS MEMORY\_OPTIMIZED\_DATA;GOALTER DATABASE StackOverflow ADD FILE (name='StackOverflow\_ram', filename='Z:\MSSQL\DATA\StackOverflow\_ram') TO FILEGROUP StackOverflow\_ram\_fg;GOSET ANSI\_NULLS ONGOSET QUOTED\_IDENTIFIER ONGOCREATE TABLE [dbo].[Votes\_InMemory\_1]([Id] [int] NOT NULL PRIMARY KEY NONCLUSTERED,[PostId] [int] NOT NULL,[UserId] [int] NULL,[BountyAmount] [int] NULL,[VoteTypeId] [int] NOT NULL,[CreationDate] [datetime] NOT NULL)WITH (MEMORY\_OPTIMIZED = ON, DURABILITY = SCHEMA\_AND\_DATA);GO/* Takes about 11 minutes on my machine: */INSERT INTO [dbo].[Votes\_InMemory\_1](Id, PostId, UserId, BountyAmount, VoteTypeId, CreationDate)SELECT Id, PostId, UserId, BountyAmount, VoteTypeId, CreationDateFROM dbo.Votes;GO
After migrating that 5GB table to In-Memory OLTP, it’s taking up 5GB RAM, right? Well, no:
Try 28GB RAM. That’s why we have a sp_Blitz check to warn about high memory usage for In-Memory OLTP:
As to why an uncompressed 5GB rowstore table takes up 28GB RAM, that’s a topic for another blog post. (Sometimes, I imagine rowstore tables being introduced today – people would think they’re the most amazing thing ever.)
What happens when SQL Server restarts?For most of us, when we restart SQL Server, our biggest concerns are things like dropping connections, losing performance metrics, and starting over again with a fresh buffer pool. In-Memory OLTP users have a bigger problem: when the SQL Server restarts, CPU goes straight to 100%, and stays there:
Ouch. Why is CPU so high? Because with In-Memory OLTP, SQL Server starts up the database by reading the In-Memory OLTP tables up into something we call “memory.” It’s using less memory than before, thank goodness – it’s “only” 13GB – but 13GB of data still takes a long time to pull from disk and reconstruct in memory. (I’m not going to go into technical terms like crash recovery phases here because it’s not necessary to convey the overall problem.)
You can see exactly how long it takes in the SQL Server logs. In this case, the StackOverflow database recovery took 50 seconds – and in this case, because our server’s got really fast storage, the storage was able to deliver data so quickly that it pegged CPU at 100% for all 50 seconds.
During startup, wait stats show a lot of wait times on XTP_PREEMPTIVE_TASK and SLEEP_DB_STARTUP:
There’s also blocking, which is kinda funny – anything that needs to query sys.databases gets blocked, like IntelliSense queries running in the background:
This happens during failovers, restores, and attaches, too.Startups, failover clustered instance failovers, and restores all exhibit the same problem: they need to start up the database, and doing that means reconstructing the In-Memory OLTP data from scratch.
This is particularly problematic for shops that use Always On Availability Groups. Say you want to add a new database into an existing cluster, and the new database happens to use In-Memory OLTP. You restore it on the primary, and … boom, the server hits 100% CPU usage for an extended period of time, which affected all the running queries on existing databases on that server.
Here’s what CPU looks like at the end of a database restore:
CPU was relatively low while the restore was processing, but once SQL Server was done writing the files, CPU went straight to 100%. Why? Because it needed to bring the database online, which meant reading the In-Memory OLTP data from disk and reconstructing the table.
Even if you just attach an In-Memory OLTP database – a process that is normally near-instantaneous – you bring SQL Server to its knees while it reads through that data and populates memory.
In my example, I don’t have indexes on the In-Memory OLTP table, but if I did, the situation would be even worse. Indexes on these tables are only kept in memory, not on disk, so they’re reconstructed from scratch at startup, failover, restore, and attach time.
Bottom line: the more In-Memory OLTP durable data you have,
and the more databases you have that use it,
the worse this gets.
Trace flags 3408 and 3459 don’t help on SQL Server 2022, at least.Among other sources, Konstantin Taranov’s excellent trace flag list reports that trace flag 3408 forces all databases to use just one thread when starting up. I don’t think I’ve ever needed to use that trace flag, but it doesn’t appear to help here. In my lab, I set up 3408 in the startup config, restarted the SQL Server, and CPU still went straight to 100%:
Furthermore, 3408 doesn’t appear to have the desired effect on SQL Server 2022 (and I didn’t bother checking on other versions, since it wouldn’t have helped my client, as they had a lot of databases with In-Memory OLTP.) Databases are still starting up with multiple threads, which would drive CPU to 100%:
Same problem with trace flag 3459, which is supposed to disable parallel redo for AGs, but that’s unrelated to database startup, as we can see by the flames:
And just because someone’s going to ask, no, setting MAXDOP to 1 has no effect on system processes like bringing databases online.
So how do we get faster startups, failovers, and restores?In theory, you could add CPUs. In practice, that’s a really expensive way to solve this problem, and it doesn’t work when you have several (or heaven forbid, dozens) of databases that use In-Memory OLTP. SQL Server doesn’t just start up one database at a time – it starts them in groups, which means you can saturate lots of CPUs on restart. If you need to dive into the internals of this, here are a couple of resources:
In theory, you could use slower storage. Bear with me for a second: if your primary concern was that other databases on the SQL Server were unusable while In-Memory OLTP databases came online, you could actually put the In-Memory OLTP filegroup on slower storage. In this 5GB Votes table example, that does actually lower CPU usage during most of the process, only driving it to 100% near the end of the process:
(Yes, I actually tested that for one particular client, who was curious.) However, that also makes the In-Memory OLTP databases take even longer to come online! In my example with a 5GB table, the database took ~80 seconds to come online instead of ~50 – making your RTO goals tougher to meet.
In practice, minimize what you keep in In-Memory OLTP durable tables. A few ways to do that:
I’m on a boat! I’m on a 10-day Panama Canal cruise, and I stopped (well, not the boat) off the coast of Florida to answer questions y’all posted at https://pollgab.com/room/brento.
We’re almost halfway through the month. How’s it coming? You keeping up?
Or are you having a tough time because your work schedule is so busy and you can’t make 30 minutes of free time?
Hey, maybe that’s because you’re a production DBA who’s … trying to do too much work manually. See what I’m getting at? Make the time, bucko – it’s free. All month long, I’m giving away modules in our newest class, Fundamentals of PowerShell for DBAs.
Here’s what you’ll be learning this week, and only these videos are free each day:
In case you didn’t know before now, go head over to the class and watching the Before the Class modules that explain how to set up your workstation to follow along. Download these resources files to help, (The slide decks are also available to paid students.)
Not patient? Need to revisit earlier modules, do the labs, or jump ahead? Recorded Class Season Pass Fundamentals holders can jump in now – it’s included with your existing membership. Enjoy!
Is your company hiring for a database position as of March 2023? Do you wanna work with the kinds of people who read this blog? Let’s set up some rapid networking here.
If your company is hiring, leave a comment. The rules:
If your comment isn’t relevant or smells fishy, I’ll delete it. If you have questions about why your comment got deleted, or how to maximize the effectiveness of your comment, contact me.
Each month, I publish a new post in the Who’s Hiring category here so y’all can get the latest opportunities.
I’m coming back to Boston for SQLSaturday this October 14th!
On Friday the 13th (muhaha), before the event, I’m teaching a one-day pre-conference workshop on Mastering Query Tuning.
You need to speed up a SQL Server app, and you’re allowed to change the queries and indexes – but not the server hardware or settings. Good news – I’ll teach you how in a day of learning and fun. Join me, Brent Ozar, as I explain how to make your SQL Server apps go faster.
We’ll cover:
Save $50 on early bird registration now. The class will be held at the Microsoft Technology Center at 5 Wayside Road, Burlington MA 01803. Seating is limited to 114 folks. I will be there in person, and it will not be recorded or broadcast online.
Other helpful links:
You’re a production database administrator responsible for the health, security, and uptime of many database servers.
You’ve been pointing and clicking your way through SSMS for years, scripting out T-SQL to files, but… when you need to do the same task repeatedly across several servers, it’s a bit of a pain.
You’ve told yourself someday you’d learn PowerShell to do repeatable, reliable automation.
That time is now, and it’s free!
All March long, I’m giving away our newest class, Fundamentals of PowerShell for DBAs. Block out a half-hour per weekday on your calendar now because on each weekday in March, a different video will be live – but just for one day only! You gotta keep up if you wanna learn for free. (I’ll be making the videos public manually, so it won’t be at an exact time – just rest assured that if you log in at the same time every day, you’ll always have a fresh video to watch. If you log in at different times each day, well … you might not. Sorry about that.)
Here’s what you’ll be learning this week:
In case you didn’t know before now, go head over to the class and watching the Before the Class modules that explain how to set up your workstation to follow along. Download these resources files to help, (The slide decks are also available to paid students.)
Not patient? Can’t wait for the rest of the videos to go live? Recorded Class Season Pass Fundamentals holders can jump in now – it’s included with your existing membership. Enjoy!
Back in 2020 before the wheels came off the world, I’d scheduled visits to Gothenburg and Oslo for their annual SQL Server events. Now that things are back to normal, it’s back on!
Data Saturday Gothenburg, Aug 26 & 28, 2023 – Monday Post-Con: Mastering Server Tuning – You’re constantly facing new performance challenges on different servers. You need to quickly diagnose a server’s bottleneck, and learn the most common ways to fix each bottleneck. Let’s tackle it together in a fast-paced 1-day version of my full 3-day class, and because we can’t cover everything, attendees get a year’s access to the full 3-day recorded class too. Learn more and register for the workshop now.
Data Saturday Oslo, Sept 1-2, 2023: Friday Pre-Con: Mastering Query Tuning – You need to speed up an existing app, and you’re allowed to change both queries & indexes, but you can’t throw hardware at it. Let’s tackle it together in a fast-paced 1-day version of my full 3-day class, and because we can’t cover everything, attendees get a year’s access to the full 3-day recorded class too. Learn more and register for the workshop now.
The clock starts now!
You’re a production database administrator responsible for the health, security, and uptime of many database servers.
You’ve been pointing and clicking your way through SSMS for years, scripting out T-SQL to files, but… when you need to do the same task repeatedly across several servers, it’s a bit of a pain.
You’ve told yourself someday you’d learn PowerShell to do repeatable, reliable automation.
All month long, I’m giving away our newest class, Fundamentals of PowerShell for DBAs. On each weekday in March, a different video will be live – but just for one day only! You gotta keep up if you wanna learn for free. (I’ll be making the videos public manually, so it won’t be at an exact time – just rest assured that if you log in at the same time every day, you’ll always have a fresh video to watch. If you log in at different times each day, well … you might not. Sorry about that.)
Here’s what you’ll be learning this week:
In case you didn’t know before now, go head over to the class and watching the Before the Class modules that explain how to set up your workstation to follow along. Download these resources files to help, (The slide decks are also available to paid students.)
Not patient? Can’t wait for the rest of the videos to go live? Recorded Class Season Pass Fundamentals holders can jump in now – it’s included with your existing membership. Enjoy!
To find out, let’s set up a simple status log table:
DROP TABLE IF EXISTS dbo.StatusLog;CREATE TABLE dbo.StatusLog (TimeItHappened DATETIME2 PRIMARY KEY CLUSTERED, Step VARCHAR(20));GO
And then let’s try a two-part transaction:
BEGIN TRAN INSERT INTO dbo.StatusLog VALUES (GETDATE(), 'Step 1'); WAITFOR DELAY '00:00:01'; BEGIN TRAN INSERT INTO dbo.StatusLog VALUES (GETDATE(), 'Step 2'); SELECT @@TRANCOUNT AS OpenTransactions;
Right now, SQL Server shows that I have 2 open transactions:
What Happens If I Roll Back?But what does “2 open transactions” mean, really? If I do a rollback, what gets rolled back? Let’s find out:
ROLLBACK;SELECT @@TRANCOUNT AS OpenTransactions;SELECT * FROM dbo.StatusLog;
The results:
Both of our transactions were rolled back, and there’s nothing left in the table.
Let’s Try It Again, but Commit This TimeRoll through the same setup code, but then commit:
BEGIN TRAN INSERT INTO dbo.StatusLog VALUES (GETDATE(), 'Step 1'); WAITFOR DELAY '00:00:01'; BEGIN TRAN INSERT INTO dbo.StatusLog VALUES (GETDATE(), 'Step 2');GOCOMMITSELECT @@TRANCOUNT AS OpenTransactions;SELECT * FROM dbo.StatusLog;
The results are a little weird:
Only one transaction is shown as open – and right now, both rows are in the results table. If we roll back now, what happens?
Even though we said we “committed” our inner transaction, it doesn’t matter – BOTH of our transactions got rolled back.
You Can’t Really Nest Transactions Like This.Think of @@TRANCOUNT as the number of times remaining that you either need to commit or roll back. If you open a bunch of nested transactions in a row, it’s up to you to commit every single one of ’em. If ANY of them are rolled back, EVERYTHING is rolled back.
This is particularly troublesome if you try to break up locking by using a bunch of little transactions wrapped in one big outer transaction. I recently had a client who thought they could:
But as you can see here, even when you commit one part of a transaction, SQL Server still isn’t quite done with it. It has to maintain those locks because if any of your open transactions are rolled back, SQL Server’s gonna roll back everything you did – even the parts you thought were finished.
Update: a couple of commenters have pointed out SAVE TRANSACTION, and I gotta say I’m not a fan of that because of the complexity, especially around lock escalation. If you choose to use that feature, read the documentation really carefully, especially around lock escalation. Saved transactions don’t release the locks once they’ve been escalated, and if you’ve been through my Mastering classes, you know how easy it is to hit lock escalation.
Aaron Bertrand posted a challenge:
We’re going to use the AdventureWorks sample database (get your copy here), where the folks in marketing requested a list of users to e-mail a new promotional campaign. The customers need to meet at least one of the following criteria:
- last placed an order more than a year ago
- placed 3 or more orders in the past year
- have ordered from a specific category in the past two weeks
These criteria don’t have to make sense! They just need to make the query a little bit more complex than your average CRUD operations.
First, we’re going to update
Sales.SalesOrderHeaderto modern times so that dates make sense relative to today. We only care aboutOrderDatehere, but there are check constraints that protect a couple of other columns (as well as a trigger that sometimes fails on db<>fiddle but that I have no energy to troubleshoot):
DISABLE TRIGGER Sales.uSalesOrderHeader ON Sales.SalesOrderHeader;GO DECLARE @months int; SELECT @months = DATEDIFF(MONTH, MAX(OrderDate), GETDATE()) FROM Sales.SalesOrderHeader; UPDATE Sales.SalesOrderHeader SET DueDate = DATEADD(MONTH, @months-1, DueDate);UPDATE Sales.SalesOrderHeader SET ShipDate = DATEADD(MONTH, @months-1, ShipDate);UPDATE Sales.SalesOrderHeader SET OrderDate = DATEADD(MONTH, @months-1, OrderDate);GO ENABLE TRIGGER Sales.uSalesOrderHeader ON Sales.SalesOrderHeader;
This stored procedure that someone wrote will now return data (without the update, it would be hard to write predictable queries based on, say, some offset from GETDATE()).
create proc sp\_find\_customers(@paramIntCategoryId INT)asselect customerid, firstname, emailaddress from( -- last placed an order more than a year ago select distinct customerid, firstname, emailaddress from sales.customer a (nolock), person.person b(nolock), person.emailaddress c(nolock) where personid = b.businessentityid and b.businessentityid = c.businessentityid and (select max(orderdate) from sales.salesorderheader (nolock) where customerid = a.customerid) < dateadd(yyyy,-1,convert(DATE,getdate())) union -- placed at least 3 orders in the past year select distinct customerid, firstname, emailaddress from person.person p (nolock) join person.emailaddress e (nolock) on p.businessentityid = e.businessentityid join sales.customer c (nolock) on personid = p.businessentityid where customerid in (select customerid from sales.salesorderheader (nolock) where orderdate between dateadd(yy, -1, convert(DATE,getdate())) and getdate() group by customerid having count(*) >= 3) union -- ordered within specified category in past two weeks select distinct customerid, firstname, emailaddress from person.emailaddress em (nolock) join person.person pp with (nolock) on em.businessentityid=pp.businessentityid join sales.customer cu (nolock) on personid = pp.businessentityid where customerid in (select top (2147483647) customerid from sales.salesorderheader oh (nolock) join sales.salesorderdetail od (nolock) on oh.salesorderid = od.salesorderid and datediff(day, oh.orderdate, convert(DATE,getdate())) <= 14 join production.product pr (nolock) on od.productid = pr.productid inner join production.productsubcategory AS sc (nolock) on pr.productsubcategoryid = sc.productsubcategoryid and sc.productcategoryid = @paramIntCategoryId order by customerid)) x order by 1
Now, it’s your turn: how many bad practices can you find in that code?Aaron’s answers are over here. I’ve turned off comments on this blog post because if you have any questions or thoughts, you should post ’em on Aaron’s blog post. The only reason I’m posting this here is that I bet a lot of y’all aren’t subscribed to Simple Talk, and I wanted to break this challenge up into two parts – the code to review, and the answers. Do not look at the answers until you’re done with your code review. Have fun!
Not all of the questions y’all post at https://pollgab.com/room/brento require long-winded responses.
Brandon: Do you seen a rise in json queries to address impedance mismatch between data/objects? I struggled with EF to produce a query that was neither simple nor complicated against a properly designed db (according to 2 experts). Dropped EF; used json query in stored proc; it was magic.
No.
Rufus: What are the best ways to determine if two large query plans have the same shape?
Put them side by side in SSMS and zoom out.
Stan Redman: What is your opinion of the SQL force encryption setting?
I don’t do security or compliance work, so I have no opinion.
Dance Monkey: Do you think there would be much interest for someone to host a PostgreSQL office hours on Youtube/Twitch?
Yes.
Eduardo: What is highest number of DBs you could safely put in an always on availability group?
Read this.
Marian: Hi Brent! Would you recommend any SQL Server Certification that would be easily recognizable? I know Microsoft retired those on SQL Server, and focus on Azure.
No.
Hondo: What is the top batch requests per second (sustained) that you have seen in the field? What were the specs for the underlying hardware to support that kind of load?
At the moment, 150K sustained, 4 socket, 64 core box with 256GB RAM. Small data set, just lots of tiny well-tuned queries that can’t effectively be cached client side.
Rooster: How do you determine the the optimal Virtual Memory page file size for bare metal Windows Server 2019 running SQL Server 2019 Enterprise?
Read this.
Sigríður: Is there anything we can do to to influence the stat sampler so that a given index key (customer in this case) is included as one of the histogram 200 steps? We know who the top 200 most important customers are by $$$$.
Yes, filtered statistics. They’re just like filtered indexes – put a where clause on ’em.
Tim: Greetings Brent. Are you winging it? Or do you sneak a peek at pollgab before starting the stream, so that you’re prepared? If you are winging it, how long did it take until you were confident in your knowledge and no longer had to prepare?
This one takes a little longer to answer, but I’m putting it in this batch because it’s fun.
Before starting the show, I look at the PollGab queue to remove anything that might be offensive or isn’t a good fit for my show (like if someone asks a MySQL question.) In the process of looking at ’em, I at least see what’s coming. I don’t ever go Google for stuff – either I know the answer, or I’m going to tell people what I’d Google, but that’s it.
How long did it take? Well, Office Hours is a really good simulation of what it’s like to be a consultant. Client staff constantly throw questions at you, and you have to be really comfortable either saying you know the answer, or saying you don’t, but you know where you would look. You can’t feel guilty about saying you don’t know. So I’ve been really comfortable with what I do know for a long time – but it’s just that the scope of that surface area slowly and steadily grew over time.
I still make mistakes! In a recent show, I said I didn’t think unique constraints also created a unique index under the hood. One of the viewers pointed out the mistake. Strangely, I get excited about that because it means I still have stuff to learn!
Thanks to this technique to run SQL Server on Apple Silicon chips, I’m now developing exclusively on my Mac! I’ve been using a Mac for over 15 years, but in the past, I’ve always used Windows at some layer somewhere. This time around, it’s all Mac the whole way down, which is kinda nifty. Makes my release process easier.
Wanna watch me use it? Take the class.To get the new version:
Consultant Toolkit ChangesI updated it to this month’s First Responder Kit, but no changes to querymanifest.json or the spreadsheet. If you’ve customized those, no changes are necessary this month: just copy your spreadsheet and querymanifest.json into the new release’s folder.
sp_BlitzCache Changes* Enhancement: when using @SortOrder = ‘all’, there’s a new pattern column to show which metrics sucked about the query, and it’s included in the table output. (#3172, thanks Adrian Buckman.) * Fix: case sensitivity issues on joining to sys.all_columns. (#3233, thanks sm8680.)
sp_BlitzFirst Changes* Fix: QRY_PROFILE_LIST_MUTEX lock timeouts on sys.dm_exec_query_statistics_xml. (#3210, thanks sqlslinger.)
sp_BlitzLock Changes* Fix: error converting data type nvarchar to bigint. (#3201, thanks Erik Darling.) * Fix: arithmetic overflow in wait_time_hms when wait time added up to more than 2147483647. (#3215, thanks Vlad Drumea.) * Fix: string or binary data would be truncated in table tempdb.dbo.#deadlock_owner_waiter. (#3206, thanks johnkurtdk.)
sp_BlitzWho Changes* Fix: shows procedure definition even if the current statement isn’t in the plan cache. (#3163, thanks Adrian Buckman.) * Fix: tempdb allocations did not include internal objects such as worktables and workfiles. (#3174, thanks Adrian Buckman.)
For SupportWhen you have questions about how the tools work, talk with the community in the #FirstResponderKit Slack channel. Be patient: it’s staffed by volunteers with day jobs. If it’s your first time in the community Slack, get started here.
When you find a bug or want something changed, read the contributing.md file.
When you have a question about what the scripts found, first make sure you read the “More Details” URL for any warning you find. We put a lot of work into documentation, and we wouldn’t want someone to yell at you to go read the fine manual. After that, when you’ve still got questions about how something works in SQL Server, post a question at DBA.StackExchange.com and the community (that includes me!) will help. Include exact errors and any applicable screenshots, your SQL Server version number (including the build #), and the version of the tool you’re working with.
Today’s episode of Office Hours is brought to you by Quest Software. I went through your top-voted questions from PollGab.com/room/brento, and, uh, kinda looked like I was sponsored by Fendi while doing it, hahaha:
Wow, those logos are bigger than I thought. Here’s what we covered:
Y’all post and upvote questions at https://pollgab.com/room/brento, and I stream sessions on my Twitch.tv channel where I answer ’em and chat with the audience.
It’s Valentine’s day – let’s do some matchmaking!
Is your company hiring for a database position as of February 2023? Do you wanna work with the kinds of people who read this blog? Let’s set up some rapid networking here. If your company is hiring, leave a comment.
The rules:
If your comment isn’t relevant or smells fishy, I’ll delete it. If you have questions about why your comment got deleted, or how to maximize the effectiveness of your comment, contact me.
Each month, I publish a new post in the Who’s Hiring category here so y’all can get the latest opportunities.
While waiting for the dry cleaner to open, I went live to stream a quiet session going through a bunch of y’all’s questions from https://pollgab.com/room/brento.
Here’s what we covered:
You’re a production database administrator responsible for the health, security, and uptime of many database servers.
You’ve been pointing and clicking your way through SSMS for years, scripting out T-SQL to files, but… when you need to do the same task repeatedly across several servers, it’s a bit of a pain.
You’ve told yourself someday you’d learn PowerShell to do repeatable, reliable automation.
That time is March, and it’s gonna be free!
All March long, I’m giving away our newest class, Fundamentals of PowerShell for DBAs. Block out a half-hour per weekday on your calendar now because on each weekday in March, a different video will be live – but just for one day only! You gotta keep up if you wanna learn for free. (I’ll be making the videos public manually, so it won’t be at an exact time – just rest assured that if you log in at the same time every day, you’ll always have a fresh video to watch. If you log in at different times each day, well … you might not. Sorry about that.)
Here’s what you’ll be learning:
Get started now by heading over to the class and watching the Before the Class modules that explain how to set up your workstation to follow along. Download these resources files to help, (The slide decks are also available to paid students.)
Not patient? Wanna get started on it right away? Recorded Class Season Pass Fundamentals holders can jump in now – it’s included with your existing membership. Go get your learn on!
We went up to Michigan to see my dad’s side of the family, and the snow came down just in time for our arrival. Always love the fresh snow look.
So I stood outside and took your top-voted questions from https://pollgab.com/room/brento. Let’s see what y’all came up with today:
Here’s what we covered:
Lots of good questions on today’s broadcast! If you’d like to submit one, go to https://pollgab.com/room/brento and upvote the ones you’d like to see me cover.
Here’s what we covered today:
Before heading out to dinner, I went through your highly-upvoted questions from https://pollgab.com/room/brento.
Here’s what we covered in this episode:
In a recent Office Hours post, David Reabow suggested letting ChatGPT answer a few of the questions y’all posted at http://pollgab.com/room/brento/. Brilliant! Let’s see how it does. I purposely cherry-picked the questions that I thought ChatGPT would have the easiest time answering.
Rollback is single threaded asks: Hi Brent! An update statement has blocked all other sessions. What should I do? Killing it, restarting SQL server service, waiting, etc. What’s your opinion? Thanks!
ChatGPT answered:
If an update statement has blocked all other sessions, there are a few options to consider:
In any case, it is advisable to take a backup of the database before taking any action to avoid data loss.
Brent says: bullet points 1 and 4 are basically the same. I abhor bullet point 3 because it doesn’t add anything that bullet point 2 wouldn’t have done. It was on the right track – bullet points 1 and 2 are good starting points.
Peter Riis: Hello Brent. We got a database storing mainly images in BLOB. Storing BLOB’s in the database got several disadvantages, so we thought about implementing FILESTREAM. What are your opinion on storing files in a database?
ChatGPT says:
Storing large files like images in a database can have the following disadvantages:
On the other hand, FILESTREAM is an option that allows you to store large binary data (BLOBs) in a database, while maintaining the efficient streaming access to that data. FILESTREAM integrates the SQL Server database engine with the NTFS file system to store and manage large BLOB data.
In conclusion, whether to store large files in a database or not depends on the specific requirements and use cases of the application. If efficient streaming access to the large files is required, FILESTREAM can be a good option. However, if the focus is on performance, scalability, and managing large files efficiently, storing the files outside the database in the file system might be a better solution.
Brent says: nailed it.
Stone Tablet Monk: When creating a new index, what is your opinion of using OPTIMIZE_FOR_SEQUENTIAL_KEY for minimizing last page insert contention? Is this a good solution? Is this the only solution?
ChatGPT says:
The OPTIMIZE_FOR_SEQUENTIAL_KEY option can be used when creating a new index to minimize last page insert contention, by prioritizing index page allocation for new data rows at the end of the index. This can help reduce index fragmentation and improve insert performance.
However, this is not the only solution to minimize last page insert contention. Other solutions include:
In conclusion, using OPTIMIZE_FOR_SEQUENTIAL_KEY is a valid solution for minimizing last page insert contention, but it should be evaluated along with other options to determine the best approach for a specific scenario.
Brent says: ChatGPT is simply flat out wrong here. Fill factor is a setting that isn’t honored during inserts. Clustered indexes are irrelevant here, too. This answer illustrates one of the challenges with AI – it can’t synthesize information on brand new terms and topics where there isn’t already conventional wisdom for it to analyze. Now, for the answer: what I’d ask is, how do you know that you have last page insert contention? This typically shows up at over 1,000 inserts per second, sustained – and most people I see asking this question, don’t even have the problem.
Piotr: When are SSMS client statistics helpful in troubleshooting query performance issues?
ChatGPT says:
SQL Server Management Studio (SSMS) client statistics can be helpful in troubleshooting query performance issues in the following situations:
To enable client statistics in SSMS, right-click the query window and select “Include Client Statistics” in the context menu. After the query has completed, the client statistics will be displayed in the results pane.
Brent says: again, ChatGPT is just bullshitting here, and most of this is just flat out wrong. Client stats don’t show wait time or blocking queries. As to the real answer – for me, client statistics haven’t been useful.
Here’s the problem with ChatGPT.It speaks with the same level of authority no matter how much or little confidence it has in its answers. It never says “I think” or “I suspect” or “Perhaps” or “I don’t know.” It just confidently struts right onstage and speaks loudly.
I stopped after four questions because I think these 4 really sum up the problem with ChatGPT today.
If you have tech questions, ChatGPT could theoretically be useful if you already know the right answers, and you can weed out the garbage. But … if you already know the right answers, what’s the point?
I do think AI has tons of good uses, like writing real estate listing text, but answering database questions isn’t one of ’em.
ChatGPT, Resource Governor, manually created stats, Always Encrypted, and as always, fragmentation: let’s answer your questions from https://pollgab.com/room/brento.
Here’s what we covered today:
Wanna learn about SQL Server and the Microsoft data platform, but you don’t wanna sit through long videos?
Enjoy short videos on TikTok?
I’ve got just the thing: I’m taking the best Q&A from Office Hours and putting ’em out as individual videos. That way, as you’re swiping through practical jokes, friendship goals, candid idiocy, music reimaginations, and dog tips, you can also learn a little in bite-sized chunks, too.
@brentozarultd Do you have any resources/tips on a new dba needing to inventory the servers/instances/databases at a company? I have around 30 servers that I want to start documenting that currently have 0 physical documentation. Thanks Brent! #sqlserver #sql #dba #database #microsoft #hacks #tips #brentozar #azure
? original sound – Brent Ozar Unlimited
For those tech answers, follow @BrentOzarULTD on TikTok. Or, if you prefer stalking my personal life, my own account is @BrentOzar.
Y’all just never run out of interesting questions at https://pollgab.com/room/brento! I’m impressed, got another great round today.
Here’s what we covered:
Twenty years ago this month (next Wednesday to be exact), sysadmins and database administrators started noticing extremely high network traffic related to problems with their SQL Servers.
The SQL Slammer worm was infecting Microsoft SQL Servers.
Microsoft had known about it and patched the problem 6 months earlier, but people just weren’t patching SQL Server. There was a widespread mentality that only service packs were necessary, not individual hotfixes.
The problem was made worse because back then, many servers were directly exposed to the Internet, publicly accessible with a minimum amount of protection. Since all the worm needed was access to port 1434 on a running SQL Server, and many folks had their servers exposed without a firewall, it spread like wildfire.
Even if only one of your corporate SQL Servers was hooked up to the Internet, you were still screwed. When that server got infected, it likely had access to the rest of your network, so it could spread the infection internally.
So what have we learned in 20 years?In terms of network security, a lot. I don’t have raw numbers, but it feels like many, many more client servers are behind firewalls these days. But… like with the original infection, all it takes is just one SQL Server at your shop to be infected, and if that one can talk to the rest of the servers in your network, you’re still screwed if something like Slammer strikes again.
In terms of patching SQL Server, to be honest, I don’t think we’ve learned very much. Most of the SQL Servers running SQL ConstantCare still aren’t patched with the latest Cumulative Updates, and many of them are several years behind in patching.
We’re just hoping that the worst bugs have been found, and no new security bugs are getting introduced.
Hope is not a strategy. Patching is. Patch ’em if you’ve got ’em.
After I stopped selling live classes, I took some time off all live broadcasting period. It was a nice couple of months over the holidays, had a good time with the family, and now I’m starting to fire up my Twitch channel again.
I’m not setting a schedule yet, just broadcasting when I have time available, so if you want to get alerted when I start streaming, subscribe to that channel and turn on notifications.
Here are the questions we discussed:
To follow along, you’ll need:
In Docker Desktop, go into Settings, Features in Development, and check the box for “Use Rosetta.” That’s the new 4.16 feature that allows you to run Intel-focused apps like Microsoft SQL Server on Apple Silicon.
sudo docker pull mcr.microsoft.com/mssql/server:2022-latest
That’ll download the ~500MB container, which takes a minute or two depending on your Internet connection. Next, start the container:
sudo docker run -e "ACCEPT\_EULA=Y" -e "MSSQL\_SA\_PASSWORD=<YourStrong@Passw0rd>" \ -p 1433:1433 --name sql1 --hostname sql1 \ -d \ mcr.microsoft.com/mssql/server:2022-latest
Don’t use an exclamation point in your password – that can cause problems with the rest of the script. (Frankly, to keep things simple, I would just stick with upper & lower case letters plus numbers.)
In the above example, the container’s name will be sql1. If you decide to get fancy and change that, remember the name – you’ll need it later.
You’ll get an error about the platform – that’s okay, ignore it. Docker Desktop will show the container as running:
And in less than a minute, you can connect to it from Azure Data Studio. Open ADS and start a new connection:
With any luck, you’ll get a connection and be able to start running queries. To have fun, we’re going to want a sample database.
cd ~/LocalOnly
If you don’t have a folder like that, you can just go into your Downloads folder:
cd ~/Downloads
Download the Stack Overflow Mini database, a small ~1GB version stored on Github:
curl -L -o StackOverflowMini.bak 'https://github.com/BrentOzarULTD/Stack-Overflow-Database/releases/download/20230114/StackOverflowMini.bak'
Make a backups folder inside your Docker container – note that if you changed the container name from sql1 to something else in the earlier steps, you’ll need to change it here as well:
sudo docker exec -it sql1 mkdir /var/opt/mssql/backup
Copy the backup file into your container:
sudo docker cp StackOverflowMini.bak sql1:/var/opt/mssql/backup
In Azure Data Studio, restore the database:
RESTORE DATABASE StackOverflowMini FROM DISK='/var/opt/mssql/backup/StackOverflowMini.bak'WITH MOVE 'StackOverflowMini' TO '/var/opt/mssql/data/StackOverflowMini.mdf', MOVE 'StackOverflowMini\_log' TO '/var/opt/mssql/data/StackOverflowMini\_log.ldf';
Presto – you now have the Stack Overflow database locally.
docker stop sql1
Because you’re exceedingly smart and almost sober, you can probably guess the matching command:
docker start sql1
And yes, the database will still be there after you stop & start it.
For Mac Users, This is a Godsend.Because this just works, at least well enough to deal with development, blogging, demoing, presenting, etc. For those of us who’ve switched over to Apple Silicon processors, this is fantastic. I love that I can work on the First Responder Kit without having to fire up a Windows VM.
This isn’t for production use, obviously, and it’s not supported in any official way. In this post, I didn’t touch on security, firewalls, SQL Agent, other versions of SQL Server, performance tuning, memory management, or anything like that, nor do I intend to get involved with any of that in Docker anyway.
This particular combination of technologies (plain Docker running SQL Server for Linux on a Mac with Apple Silicon processors) is brand spankin’ new as of last week, so there isn’t anything out on the web in the way of troubleshooting. However, if you want to learn more about the two components that are probably the most new to you (Docker and SQL Server for Linux), subscribe to Anthony Nocentino of Nocentino.com. He’s the go-to person for SQL Server on containers & Linux. He’s got several Pluralsight courses on these, too.
I am waaaay overdue for a haircut, but instead of being a responsible adult, I stopped to take your questions from https://pollgab.com/room/brento.
Normally, y’all post and upvote great questions at https://pollgab.com/room/brento, but in today’s episode, y’all upvoted some stinkers. Buckle up.
Writing new code = bugging. That part’s easy.
Taking those bugs back out, that’s the hard part.
Developers are used to their tools having built-in ways to show what line of code is running now, output the current content of variables, echo back progress messages, etc. For a while, SQL Server Management Studio also had a debugger, but it was taken out of SSMS v18 and newer versions. Even when it was around, though, I wasn’t a big fan: SQL Server would literally stop processing while it stepped through your query. This was disastrous if your query was holding out locks that stopped other peoples’ queries from moving forward – and you just know people were using it in production.
I do wish we had an easy, block-free way of doing T-SQL debugging in production, but T-SQL debugging is different than debugging C# code. So if your T-SQL code isn’t doing what you expect, here are a few better ways to debug it.
Option 1: Use PRINT statements.Since the dawn of time, developers have put in lines like this:
BEGIN TRANPRINT 'Starting access date changes'UPDATE dbo.UsersSET LastAccessDate = GETDATE()WHERE DisplayName = N'Brent Ozar';PRINT 'Done with access date, starting reputation changes'UPDATE dbo.UsersSET Reputation = Reputation / 0WHERE DisplayName = N'jorriss';PRINT 'Done with reputation changes'COMMIT
So that when the statement fails, they can at least see which part failed:
There are a few problems with this approach:
Let’s raise our game with RAISERROR.
Option 2: Use RAISERROR, pronounced raise-roar.What? You didn’t notice that it’s misspelled? Okay, confession time, I didn’t realize that either – Greg Low of SQLDownUnder pointed it out to me. Let’s add a little more complexity to our code:
DECLARE @Debug BIT = 1;BEGIN TRANIF @Debug = 1RAISERROR (N'Starting access date changes', 0, 1) WITH NOWAITUPDATE dbo.UsersSET LastAccessDate = GETDATE()WHERE DisplayName = N'Brent Ozar';IF @Debug = 1RAISERROR (N'Done with access date, starting reputation changes', 0, 1) WITH NOWAITUPDATE dbo.UsersSET Reputation = Reputation / 0WHERE DisplayName = N'jorriss';IF @Debug = 1RAISERROR (N'Done with reputation changes', 0, 1) WITH NOWAITCOMMIT
I’ve added a @Debug parameter, and my status messages only print out when @Debug = 1. Now, in this example, I don’t really need a parameter – but in your real-world stored procedures and functions, you’re going to want one, and you’ll want the default value set to 0, like this:
CREATE OR ALTER PROC dbo.DoStuff @MyParam VARCHAR, @Debug BIT = 0 AS...
That way, you only turn on the debug features manually when you need ’em, but the app doesn’t call @Debug, so it just gets left at its default value, 0.
I’ve also switched to RAISERROR instead of PRINT because RAISERROR has a handy “WITH NOWAIT” parameter that tells SQL Server to push out the status message to the client right freakin’ now rather than waiting for a buffer to fill up.
When you’re troubleshooting long or complex processes, you’re probably going to want to dynamically drive the status message. For example, say it’s a stored procedure that takes hours to run, and you wanna see which parts of it took the longest time to run. You’re not gonna sit there with a stopwatch, and you’re not gonna come back later hoping that the queries will still be in the plan cache. Instead, you wanna add the date/time to the RAISERROR message.
Unfortunately, RAISERROR doesn’t support string concatenation. Instead, you have to pass in a single string that has everything you want, like this:
DECLARE @Now NVARCHAR(50);SET @Now = CONVERT(NVARCHAR(50), GETDATE(), 26);RAISERROR (N'Done with reputation changes at %s', 0, 1, @Now) WITH NOWAIT
Which gives you the date at the end of the output:
You can even pass multiple arguments in – check out the RAISERROR syntax for more details on how the arguments work.
Option 3: Use Table Variables.You’ve probably heard advice from me or others warning you that table variables lead to bad performance. That’s true in most cases – although sometimes they’re actually faster, as we discuss in the Fundamentals of TempDB class. However, table variables have a really cool behavior: they ignore transactions.
BEGIN TRANDECLARE @Progress TABLE (StatusDate DATETIME2, StatusMessage NVARCHAR(4000));INSERT INTO @Progress VALUES (GETDATE(), N'A one');INSERT INTO @Progress VALUES (GETDATE(), N'And a two');ROLLBACKSELECT * FROM @Progress ORDER BY StatusDate;
So even though I did a rollback, not a commit, I still get the contents of the table variable:
This is useful when you’re:
And there you have it – 3 ways to work through debugging without using the discontinued SSMS Debugger. I typically use RAISERROR myself – it’s easy enough to implement, and it’s a technique you’ll use forever. There are more ways, too, and you’re welcome to share your favorite way in the comments.
When someone says, “Find all the rows that have been deleted,” it’s a lot easier when the table has an Id/Identity column. Let’s take the Stack Overflow Users table:
It has Ids -1, 1, 2, 3, 4, 5 … but no 6 or 7. (Or 0.) If someone asks you to find all the Ids that got deleted or skipped, how do we do it?
Using GENERATE_SERIES with SQL Server 2022 & NewerThe new GENERATE_SERIES does what it says on the tin: generates a series of numbers. We can join from that series, to the Users table, and find all the series values that don’t have a matching row in Users:
DECLARE @FirstId INT, @LastId INT;SELECT @FirstId = MIN(Id),@LastId = MAX(Id)FROM dbo.Users;SELECT gs.valueFROM GENERATE\_SERIES(@FirstId, @LastId, 1) gsLEFT OUTER JOIN dbo.Users u ON gs.value = u.IdWHERE u.Id IS NULL;
The LEFT OUTER JOIN seems a little counter-intuitive the first time you use it, but works like a champ:
What’s that, you ask? Why does GENERATE_SERIES have fuzzy underlines? Well, SQL Server Management Studio hasn’t been updated with the T-SQL syntax that came out in the last release.
Thankfully, Microsoft separated the setup apps for SSMS and the SQL Server engine itself for this exact reason – the slow release times of SSMS were holding back the engine team from shipping more quickly, so they put the less-frequently-updated SSMS out in its own installer.
(Did I get that right? Forgive me, I’m not a smart man.)
Using Numbers Tables with Older VersionsIf you’re not on SQL Server 2022 yet, you can create your own numbers table with any of these examples. Just make sure your numbers table has at least as many rows as the number of Ids you’re looking for. Here’s an example with a 100,000,000 row table:
DROP TABLE IF EXISTS dbo.Numbers;CREATE TABLE dbo.Numbers (Number INT PRIMARY KEY CLUSTERED); INSERT INTO dbo.Numbers(Number)SELECT TOP 10000000 row\_number() over(order by t1.number) as NFROM master..spt\_values t1 CROSS JOIN master..spt\_values t2 CROSS JOIN master..spt\_values t3GO
Then, we’ll use that in a way similar to GENERATE_SERIES:
DECLARE @FirstId INT, @LastId INT;SELECT @FirstId = MIN(Id),@LastId = MAX(Id)FROM dbo.Users;SELECT n.NumberFROM dbo.Numbers nLEFT OUTER JOIN dbo.Users u ON n.Number = u.IdWHERE u.Id IS NULL AND n.Number > @FirstId AND n.Number < @LastIdORDER BY n.Number;
That produces similar results, but not identical:
What’s different? Well, this method didn’t include 0! When I populated my numbers table, I only built a list of positive integers. The single most common mistake I see when using numbers tables is not having thorough coverage of all the numbers you need. Make sure it goes as low and as high as the values you need – a problem we don’t have with GENERATE_SERIES, since we just specify the start & end values and SQL Server takes care of the rest.
If you’d like to dive deeper into other ways to solve this problem, Itzik Ben-Gan’s chapter on Gaps & Islands will be right up your alley. Me, though, I’ll call it quits here because I’m in love with GENERATE_SERIES to solve this problem quickly and easily. Also, I’m lazy.
Here’s what I wrote in 2022 that gathered the most views:
Evergreen Posts You Kept ReadingThese aren’t posts I wrote in 2022 – they’re older posts that have stood the test of time, and keep showing up in Google results. These tutorial posts aren’t often the favorites of readers when the post first goes live, but they’re the kinds of posts that bring in new readers over time. I’ve gradually updated a lot of these (even if I wasn’t the original author) because they’re consistently popular.
Not only is it hard to write posts like this initially, but it takes work to continue to refine the content over time, adding in the kinds of key words and content that people are searching for. I actively prune some of ’em, and some of them were perfect when they were published.
Top YouTube Videos You WatchedMy YouTube channel got 560,871 views in 2022, with 101,500 watch hours and 39,000 subscribers. I have to confess that I do a terrible job of reminding viewers to smash that like button and hit the bell to be notified when new posts go live. I’m just not that kind of host – yet, hahaha.
Here’s to a productive 2023 where I share a lot and y’all learn a lot!
Are your peers being paid more this year? Are they switching job roles? Are they planning on leaving their companies? To find out, I run a salary survey every year for folks in the database industry. Download the raw data here and slice & dice ’em to see what’s important to you.
Salaries are on the rise again this year:
If I filter the data for just the United States, they’re on the rise too:
And if I filter the data for just DBAs, the job title I’m usually most interested in, United States DBAs are on the rise by about 6% this year:
Nice! Congrats on the raises, y’all. (On the other hand, if you’re reading this, and you didn’t get a raise, now’s the time to download this data and start having a discussion with your manager.) You might also be considering switching jobs – so which job titles are getting the most this year?
Architects, app code developers, and managers are doing well. Note, though, that I’m filtering on just United States folks here, so the survey sample size is getting smaller. Plus, I would never make long term career decisions based on money alone – you might not be happy doing a particular job. (I hated management.)
Is any one job title growing at the expense of others? Are people leaving DBA work en masse and switching to something else? No, the mix by job title has been roughly the same over the years, despite what you might have heard from Thought Leaders™:
So why do you hear Thought Leaders™ saying one job role or another is dead, and another one is on fire? Because it’s a short-term publicity trend, sometimes as short as months, where a few publication outlets run the same stories and build buzz around something in order to get clicks.
Is there a mass resignation coming? What are peoples’ career plans for 2023, and have those numbers changed from prior years?
Most of you still want to stay with the same employer, in the same role. Well, in that case, here’s to a stable, safe 2023 for y’all.
Was your New Year’s resolution to get a new job? Heads up: the comments in this post are for you.
Is your company hiring for a database position as of January 2023? Do you wanna work with the kinds of people who read this blog? Let’s set up some rapid networking here. If your company is hiring, leave a comment.
The rules:
If your comment isn’t relevant or smells fishy, I’ll delete it. If you have questions about why your comment got deleted, or how to maximize the effectiveness of your comment, contact me.
Each month, I publish a new post in the Who’s Hiring category here so y’all can get the latest opportunities.
Beep beep! Here’s a speed round of Office Hours where I rip through a dozen questions in under ten minutes. Want to see your own questions answered? Post ’em and upvote the ones you like at https://pollgab.com/room/brento.
Ever wonder how fast people are adopting new versions of SQL Server, or what’s “normal” out there for SQL Server adoption rates, hardware sizes, or numbers of databases? Let’s find out in the winter 2022 version of our SQL ConstantCare® population report.
Out of 3,679 monitored servers, here’s the version adoption rate:
The big ones:
Companies are leapfrogging right past SQL Server 2017. I’m going to hazard a guess that SQL Server 2017 came out too quickly after 2016, and didn’t offer enough features to justify upgrades from 2016.
Does that offer us any lessons for SQL Server 2022? Is 2022 going to be a 2017-style release that people just leapfrog over? Well, as I write this, it’s late December 2022, and I’m not seeing the widespread early adoption that I saw for 2019 where people had it in development environments ahead of the release, learning how to use it.
Me personally, one of the most awesome features of 2022 is the ability to fail back and forth between SQL Server and Azure SQL DB Managed Instances. However, that feature is still in limited public preview that requires a signup. Combine that with the fact that both 2022 and Managed Instances have really low adoption rates, and … I just don’t think this feature is going to catch on quickly. (As a blogger/speaker/trainer, that’s useful information, too – I only have so many hours in the day, and I gotta write material for things I think people are actually going to adopt.)
Okay, next up – adoption trends over time. You’re going to be tempted to read something into this chart, but I need to explain something first: we saw a huge drop in Azure SQL DB users for SQL ConstantCare. In the past survey, we had exactly 500 Azure SQL DBs being monitored – and this round, it dropped to just 64. I talked briefly with a couple of the SaaS customers who stopped monitoring their databases, and they both said the same thing: “We’re not going to change the app’s code or indexes based on what you found, so we’re not going to monitor it further.” That’s fair – throwing cloud at it is a perfectly legit strategy. So now, having said that, let’s see the trends:
This quarter’s numbers are a little misleading because it looks like SQL Server 2019 stole Azure’s market share – but now you know why. If I look at pure installation numbers (not as a percentage):
The low early adoption rate of 2022 is more interesting to me when I combine it with another number: Availability Groups adoption is 26%, kinda. Of the production SQL Servers that can (2012 & newer, non-Azure, etc), 26% have turned on the Always On Availability Groups feature. Note that I didn’t say 26% of databases are protected, nor 26% of data volume – just 26% of the servers have the feature turned on, period, and that says something because the feature isn’t on by default, and requires a service restart to take effect. Actual databases protected is way, way less.
One of SQL Server 2022’s flagship features is Managed Instance link, the ability to fail over databases back & forth between your SQL Server 2022 instances and Azure SQL DB Managed Instance. In theory, that’s awesome. In practice, I’ve never seen a concise live demo setting it up, failing it over, and failing it back. The setup part 1 and part 2 doesn’t look terrible, and the failover looks fairly straightforward, but … there are no docs on troubleshooting it. Between the low adoption rates, the complexity of existing AGs, the complexity of cloud networking, and this brand new feature, I’m … not ready to dig into Managed Instance link anytime soon.
I totally appreciate those of y’all who have the guts to try it, though, especially in production. I think that kind of thing is the future of hybrid databases. Looking at the current population numbers, though, it’s a pretty far-off future.
Y’all post questions at https://pollgab.com/room/brento and upvote the ones you’d like to see me discuss, and then I artfully dodge giving you answers. At least, that’s how it feels sometimes, hahaha:
Here’s what we discussed in today’s episode:
If you’ve been following along with this week’s posts on DATETRUNC and STRING_SPLIT, you’re probably going to think the answer is no, but bear with me. It’s Christmas week, right? The news can’t all be bad.
GREATEST and LEAST are kinda like MAX and MIN, but instead of taking multiple rows as input, they take multiple columns. For example:
SELECT GREATEST(1,2,3) AS TheGreatest,LEAST(1,2,3) AS TheLeastest;
Produces 3 and 1. This actually has really useful real-world implications.
Let’s take the Stack Overflow database, and let’s say I want to find any posts (questions or answers) that had recent activity. This is surprisingly difficult because Posts has 2 date columns: LastEditDate, and LastActivityDate. You would think that LastActivityDate would be the most recent, but you would be incorrect – when posts are edited, the LastEditDate is set, but LastActivityDate is not.
So if I was looking for any Posts that were active – either via reader activity, or edits – in a date range, I used to have to build supporting indexes, and then write queries like this:
/* Create supporting indexes */CREATE INDEX LastActivityDate\_LastEditDateON dbo.Posts(LastActivityDate, LastEditDate);CREATE INDEX LastEditDate\_LastActivityDateON dbo.Posts(LastEditDate, LastActivityDate);GO/* The old way: */SELECT TOP 200 *FROM dbo.PostsWHERE LastActivityDate >= '2018-05-27' OR LastEditDate >= '2018-05-27'ORDER BY Score DESC;/* The SQL Server 2022 way: */SELECT TOP 200 *FROM dbo.PostsWHERE GREATEST(LastActivityDate, LastEditDate) >= '2018-05-27'ORDER BY Score DESC;
I’m using 2018-05-27 because my copy of the Stack Overflow database’s last activity is in 2018-06-03. Depending on which version you’re using, if you’re trying to reproduce these results, pick a date that’s within the last week of activity
So, what’s better – the old way or the new way? Like your hips, the actual execution plans don’t lie:
The old way smokes the SQL Server 2022 way. I mean, it’s not even close. The old way splits up the work into two index seeks, one on LastActivityDate and one on LastEditDate. It finds the recent stuff, does the appropriate key lookups, and it’s done in one second.
The new way does a table scan and takes a minute and a half.
Its estimates and its index usage are just garbage, and I don’t mean like Shirley Manson.
Again – they’re not necessarily bad functions, but they have no business in the FROM clause and below. Don’t use them in WHERE, don’t use them in JOINS, just use them to construct output, variables, and strings.
SQL Server 2022 improved the STRING_SPLIT function so that it can now return lists that are guaranteed to be in order. However, that’s the only thing they improved – there’s still a critical performance problem with it.
Let’s take the Stack Overflow database, Users table, put in an index on Location, and then test a couple of queries that use STRING_SPLIT to parse a parameter that’s an incoming list of locations:
CREATE INDEX Location ON dbo.Users(Location);SET STATISTICS IO ON;GOCREATE OR ALTER PROC dbo.usp\_GetUsersByLocation\_Subquery@LocationList NVARCHAR(4000) ASSELECT TOP 1000 u.*FROM dbo.Users uWHERE u.Location IN (SELECT value FROM STRING\_SPLIT(@LocationList, N',', 1))ORDER BY u.Reputation DESC;GOCREATE OR ALTER PROC dbo.usp\_GetUsersByLocation\_Join@LocationList NVARCHAR(4000) ASSELECT TOP 1000 u.*FROM STRING\_SPLIT(@LocationList, N',', 1) lINNER JOIN dbo.Users u ON l.value = u.LocationORDER BY u.Reputation DESC;GOEXEC usp\_GetUsersByLocation\_Subquery N'India,China';EXEC usp\_GetUsersByLocation\_Join N'India,China';
The two queries produce slightly different actual execution plans, but the way STRING_SPLIT behaves is the same in both, so I’m just going to take the first query to use as an illustration:
That red-highlighted part has two problems:
As a result, everything else in the query plan is doomed. The estimates are all garbage. SQL Server will choose the wrong indexes, process the wrong tables first, make the wrong parallelism decisions, be completely wrong about memory grants, you name it.
Like I wrote in this week’s post about DATETRUNC, that doesn’t make STRING_SPLIT a bad tool. It’s a perfectly fine tool if you need to parse a string into a list of values – but don’t use it in a WHERE clause, so to speak. Don’t rely on it to perform well as part of a larger query that involves joins to other tables.
Working around STRING_SPLIT’s problemsOne potential fix is to dump the contents of the string into a temp table first:
CREATE OR ALTER PROC dbo.usp\_GetUsersByLocation\_TempTable@LocationList NVARCHAR(4000) ASBEGINSELECT valueINTO #LocationListFROM STRING\_SPLIT(@LocationList, N',', 1);SELECT TOP 1000 u.*FROM dbo.Users uWHERE u.Location IN (SELECT value FROM #LocationList)ORDER BY u.Reputation DESC;ENDGOEXEC usp\_GetUsersByLocation\_TempTable N'India,China';
And the actual execution plan is way better than the prior examples. You can see the full plan by clicking that link, but I’m just going to focus on the relevant STRING_SPLIT section and the index seek:
This plan is better because:
Woohoo! Just remember that temp tables are like OPTION (RANDOM RECOMPILE), like I teach you in this Fundamentals of TempDB lecture.
SQL Server 2022 introduced a new T-SQL element, DATETRUNC, that truncates parts of dates. For example:
SELECT DATETRUNC(year, '2017-06-01');
Truncates everything in that date other than the year, so it returns just 2017-01-01 00:00:
You might ask, “Well, why not just use YEAR()?” That’s a good question – there are times when you need a start or end date for a date range, and this could make it easier than trying to construct a full start & end date yourself.
Easier for you, that is – but not necessarily good for performance. Let’s take the Stack Overflow database, Users table, put in an index on LastAccessDate, and then test a few queries that are logically similar – but perform quite differently.
CREATE INDEX LastAccessDate ON dbo.Users(LastAccessDate);SET STATISTICS IO ON;GOSELECT COUNT(*) FROM dbo.UsersWHERE LastAccessDate >= '2017-01-01' AND LastAccessDate < '2018-01-01';SELECT COUNT(*) FROM dbo.UsersWHERE YEAR(LastAccessDate) = 2017;SELECT COUNT(*) FROM dbo.UsersWHERE DATETRUNC(year, LastAccessDate) = 2017;
And check out their actual execution plans:
The first one, passing in a specific start & end date, gets the best plan, runs the most quickly, and does the least logical reads (4,299.) It’s a winner by every possible measure except ease of writing the query. When SQL Server is handed a specific start date, it can seek to that specific part of the index, and read only the rows that matched.
DATETRUNC and YEAR both produce much less efficient plans. They scan the entire index (19,918 pages), reading every single row in the table, and run the function against every row, burning more CPU.
SQL Server’s thought process is, and has always been, “I have no idea what’s the first date that would produce YEAR(2017). There’s just no way I could possibly guess that. I might as well read every date since the dawn of time.”
That’s idiotic, and it’s one of the reasons we tell ya to avoid using functions in the WHERE clause. SQL Server 2022’s DATETRUNC is no different.
So why doesn’t Microsoft fix this?YEAR and DATETRUNC are tools, just like any other tool in the carpenter’s workshop. There are lots of times you might need to manipulate dates:
DATETRUNC in the SELECT isn’t so bad.Let’s use it in the SELECT clause to group users together by their last access date. Say we want a report to show trends over time. Here are two ways to write the same basic idea of a query:
SELECT YEAR(LastAccessDate) AS CreationYear,MONTH(LastAccessDate) AS CreationMonth,SUM(1) AS UsersInvolvedFROM dbo.UsersGROUP BY YEAR(LastAccessDate), MONTH(LastAccessDate)ORDER BY 1, 2;SELECT DATETRUNC(MONTH, LastAccessDate) AS CreationMonth,SUM(1) AS UsersInvolvedFROM dbo.UsersGROUP BY DATETRUNC(MONTH, LastAccessDate)ORDER BY 1;
The two queries do show the date in two different ways, but the UsersInvolved count is the same – it’s just different ways of rendering the same data:
When you review their actual execution plans, the first one (YEAR/MONTH) is much more complex, and goes parallel to chew through about 4 seconds of CPU time:
Whereas the new DATETRUNC syntax has a cool benefit: it only produces one value (the date), and the data in the index is already sorted by that column. Because of that, we don’t need an expensive sort in the execution plan. And because of that, we don’t need parallelism, either, and we only chew through about two seconds of CPU time. Nifty!
So should you use DATETRUNC? Like with most functions, the answer is yes in the select, but probably not in the FROM/JOIN/WHERE clauses.
Every time I think, “There can’t be any more SQL Server questions left,” y’all post more great ones at https://pollgab.com/room/brento!
Here’s what we covered:
Santa’s elves took a break from building toys and shipped a new version of the First Responder Kit. There are some great improvements in here, like a makeover for sp_BlitzLock and much better PSPO compatibility for sp_BlitzCache.
Wanna watch me use it? Take the class.To get the new version:
Consultant Toolkit ChangesI updated it to this month’s First Responder Kit, but no changes to querymanifest.json or the spreadsheet. If you’ve customized those, no changes are necessary this month: just copy your spreadsheet and querymanifest.json into the new release’s folder.
sp_AllNightLog Changes* Fix: if a database exists and it’s not being log shipped, skip it rather than overwrite it. (#3187, thanks David Wiseman.)
sp_Blitz Changes* Fix: date conversion errors on non-US environments. (#3161, thanks Christian Specht and Ralf Pickel.)
sp_BlitzCache Changes* Enhancement: show a statement’s parent stored procedure where available in SQL Server 2022 to work around the PSPO problem. (#3176, thanks Cody Konior.) * Fix: if you asked for the output of @SortOrder = ‘unused grant’ to be written to table, it wasn’t working. (#3160, thanks Petr-Starichenko.)
sp_BlitzFirst Changes* Fix: Managed Instances were getting errors on Perfmon counters. (#3184, thanks Cody Konior.) * Fix: lock timeout errors from sp_msforeachdb. (#3180, thanks Cody Konior.)
sp_BlitzIndex Changes* Enhancement: added support for output to table for all modes. (#2774, thanks Tor-Erik Hagen.) * Fix: the columnstore index visualization wasn’t filtering out tombstone rowgroups. (#3189) * Fix: statistics with oddball names would throw errors. (#3162, thanks Jay Holliday.)
sp_BlitzLock Changes* Enhancement: in Erik’s words, he gave the T-SQL a mommy makeover, including the ability to read from the ring buffer, improve performance, fix data duplicatoin bugs, clean XML to avoid character parsing errors, etc. (#3166, thanks Erik Darling.)
sp_BlitzQueryStore Changes* Fix: errors on TRY_CONVERT for databases in compat level 100. (#3155, thanks ray13eezy and Erik Darling.)
sp_BlitzWho Changes* Enhancement: move session_id and blocking_session_id next to each other for easier blocking troubleshooting. (#3159, thanks David Hooey.)
sp_DatabaseRestore Changes* Fix: full backups with more than 9 files were being ignored. (#3156, thanks Wilfred van Dijk.) * Fix: wasn’t working on SQL Server 2022 due to new undocumented columns. (#3190)
Bonus changes: Anthony Green kept up the tireless work of keeping the SQL Server versions file up to date.
For SupportWhen you have questions about how the tools work, talk with the community in the #FirstResponderKit Slack channel. Be patient: it’s staffed by volunteers with day jobs. If it’s your first time in the community Slack, get started here.
When you find a bug or want something changed, read the contributing.md file.
When you have a question about what the scripts found, first make sure you read the “More Details” URL for any warning you find. We put a lot of work into documentation, and we wouldn’t want someone to yell at you to go read the fine manual. After that, when you’ve still got questions about how something works in SQL Server, post a question at DBA.StackExchange.com and the community (that includes me!) will help. Include exact errors and any applicable screenshots, your SQL Server version number (including the build #), and the version of the tool you’re working with.
Wow, y’all have been posting some great questions at https://pollgab.com/room/brento lately!
Here’s what we covered today:
Is your company hiring for a database position as of December 2022? Do you wanna work with the kinds of people who read this blog? Let’s set up some rapid networking here. If your company is hiring, leave a comment.
The rules:
If your comment isn’t relevant or smells fishy, I’ll delete it. If you have questions about why your comment got deleted, or how to maximize the effectiveness of your comment, contact me.
Each month, I publish a new post in the Who’s Hiring category here so y’all can get the latest opportunities.
I was helping a client with a query, and I’m going to rework the example to use the Stack Overflow database for easier storytelling. Say we need to: Find all the locations where users have logged in since a certain date, then Return the total count of people who live in those locations One way...
I took a break from my Black Friday sale customer support emails to check in on the questions you posted at https://pollgab.com/room/brento and answer the highest-voted ones: 00:00 Start 00:15 Meshulam: What are the top use cases for running SQL Server in a container? Do many of your customers run SQL Server in a container?...
My Black Friday sale is in its last days, so most of my time at the moment is spent keeping an eye on the site and answering customer questions. I’m happy to say it’s our best year so far, too! Y’all really like the new access-for-life options. I took a break from the online frenzy to...
When will Microsoft officially release SQL Server 2023 (or 2024, or whatever it’ll be) for download? The dust is just barely starting to settle on the 2022 box, so it’s time to guess the next one. Leave one – and only one – comment here in YYYY/MM/DD format with your release date guess. If you...
Whew. This is a crazy time, isn’t it? The aftermath of the pandemic is kicking in, and there are layoffs happening in Silicon Valley companies, but… the data profession is on fire. I don’t know anyone who’s been out of work for too long because companies are so desperate to get data help. So it’s time...
I’m back home in Vegas, back down to Inbox Zero, and I’ve had some time to think about last week’s conference in Seattle. I liveblogged the keynotes, but I wanted to talk about the conference overall. This was Redgate’s first time running an in-person Summit after the professional organization folded in December 2020. Redgate bought...
Today at the PASS Data Community Summit in Seattle, Kimberly Tripp is doing the first-ever in-person community keynote. Here’s the abstract: Over the 30+ years I’ve been working with SQL Server, I’ve seen an incredible amount of innovation and change. How do we keep up with so many changes and how do we know how...
I’m in Seattle for the PASS Data Community Summit, and the day 2 keynote is about to start. This year, now that Redgate owns the Summit event, they’re doing the day 2 keynote. Gotta pay the bills, I suppose! Redgate’s CEO, Jakub Lamik, is leading a team of speakers for the keynote, and here’s the...
For the final release of SQL Server 2022, Microsoft popped a surprise that wasn’t in the release candidates: Your choices are: Install a free edition: Evaluation (which times out after 180 days), Developer (which isn’t allowed to be used for production purposes), or Express (which is limited to small databases & hardware resources) Use pay-as-you-go-billing,...
If you’ve been reading this blog for a while or subscribed to my email newsletters, you’ve learned something from Hugo Kornelis. Hugo is the wildly productive and selfless guy behind the Execution Plan Reference, SQLServerFast.com, tons of SQLBits sessions, videos on YouTube, and he’s @Hugo_Kornelis. He’s unbelievably upbeat and positive. He loves what he does, and...
Microsoft released SQL Server 2022 today, November 16, 2022. The footnotes indicate: SQL Server 2022 free editions (Developer edition, Express edition) are available to download starting today. SQL Server 2022 paid editions (Enterprise edition, Standard edition) will be available in Volume Licensing (Enterprise Agreement, Enterprise Agreement Subscriptions) customers starting today, which represents the majority of...
I’m in Seattle for the biggest annual gathering of Microsoft data platform professionals, the PASS Data Community Summit. This is the first in-person Summit since the pandemic, and the first since Redgate took over ownership from the old PASS organization. I’m really excited – this is like a family reunion for me. Microsoft’s Rohan Kumar...
I think I’ve found a bug in SQL Server setup’s MAXDOP calculation, and I need you to take a second look. Setup is recommending MAXDOP 8: Which is odd, because this is running on an AWS i3.16xlarge with 2 sockets, 2 NUMA nodes, 32 logical processors per node, 64 logical processors altogether. In this screenshot, I’ve laid...
Is your company hiring for a database position as of November 2022? Do you wanna work with the kinds of people who read this blog? Let’s set up some rapid networking here. If your company is hiring, leave a comment. The rules: Your comment must include the job title, and either a link to the full...
Ask questions at https://pollgab.com/room/brento and upvote the ones you’d like to see me cover. Here’s what we covered today: 00:00 Start 00:59 neil: is it dangerous/risky to expand a drive in azure with SQL data files on it? sql on azure vm. dont know what happens behind the scenes 02:07 Brett: I am a consultant...
Post your questions at https://pollgab.com/room/brento and upvote the ones you’d like to see me discuss during my live streams. This week, I took a break from working on my PASS Summit sessions in order to chat: Here’s what we covered: 00:00 Start 00:32 Jeremy: We have an older web app we’re going to be rewriting...
This month on the blog is Community Tools Month, and I’m going to be talking about some of the most useful and influential tools out there. You can’t learn them all – I can’t learn them all – because there just aren’t enough hours in the day to do your work, keep your skills sharp,...
Not all of the questions y’all post at https://pollgab.com/room/brento require long, thought-out answers. Some are just one-line specials, like these: F’legend: Hi Brent, in reply to a question talking about database restores for 1TB+ you also touched on scrubbing or synthetic data as a way to populate dev environments. Are there any resources/tools you would...
Is your company hiring for a database position as of September 2022? Do you wanna work with the kinds of people who read this blog? Let’s set up some rapid networking here. If your company is hiring, leave a comment. The rules: Your comment must include the job title, and either a link to the full...
What does fragmentation mean? How does it happen? Can you fix it with fill factor? Should you rebuild your indexes to fix it? At the SQLBits conference, I tried a new way of explaining it using markers and pieces of whiteboard paper as database pages, and doing inserts live on the fly. What you see...