sp_Blitz is our open source free SQL Server health check script.
From a really high level, here’s what it does:
- Create #BlitzResult, a temp table to hold its output
- Check something on your SQL Server, and if it’s found, insert a row into #BlitzResult
- Check something else, and insert a row if it’s found
- Do that a whole bunch of times
- Finally, SELECT * FROM #BlitzResult
In this blog post, I’m going to give you a guided tour, and then over a few more posts, dive into some more interesting features.
TOURSTOP01: Creating #BlitzResult to Hold Data
If you’d like to follow along in the current sp_Blitz source code, do a search for TOURSTOP01, and you’ll come to this portion of the code:
I’m using TOURSTOPXX rather than line numbers because line numbers will change over time.
The best way to explain #BlitzResults is to show you what comes out of sp_Blitz at the end:
That should look pretty familiar. Everything about sp_Blitz centers around populating #BlitzResults, and then showing it to the users.
So how do we populate #BlitzResult? Let’s go on to…
TOURSTOP02: Populating a Row in #BlitzResult
Search for TOURSTOP02, and bathe in the glory of CheckID #7:
Eagle-eyed readers will notice that there’s additional code above and beyond what’s shown here in the screenshot – more on that later. Let’s focus on just this screenshot for now.
If there are any stored procedures that run on startup, insert them into #BlitzResults along with an explanation.
This check inserts a row for each problem that it finds because startup-level stored procs are a Pretty Big Deal, and you probably want to know about all of them. However, that’s not the only way sp_Blitz checks work:
- Some checks only insert a single row if they find a problem – say, a server-level configuration issue
- Some checks insert a summary row with the number of problems they found – like heaps, because you don’t want to know about every single active heap, but just a total
TOURSTOP03: Running Checks in Every Database
If @CheckUserDatabaseObjects = 1 (and it is by default), then sp_Blitz checks inside databases too. Here’s an example:
We’re using the known-crappy sp_MSforeachdb to loop through all databases and run a check. When we first started, it was the only way we could guarantee it’d work across everybody’s server. I’m not happy about that – sp_MSforeachdb hits every database, with no options to skip them, except that also sometimes it just skips databases for no reason.
Aaron Bertrand wrote a more flexible and reliable sp_MSforeachdb, and recently he was kind enough to add that to the open source First Responder Kit. We have an open Github issue to change the parameters to make it a drop-in replacement for sp_MSforeachdb – making the parameters and output compatible – but I haven’t had the chance to work on that yet. So for now, sp_MSforeachdb it is, but eventually we’d like to switch sp_Blitz over to Aaron’s new proc.
sp_MSforeachdb changes into each user database (sorta, but you still usually need to USE the database), then executes your query.
To make doggone sure we’re in the right database, we start with USE [?]; – and sp_MSforeachdb drops in your database’s name, like USE [StackOverflow];.
Then, we run the query we want to run, which is an insert into #BlitzResults if we find any problems that match our sp_Blitz check. In TOURSTOP03, we’re looking for databases that don’t have Query Store turned on yet. (I’m a big fan of that feature.)
All database-level checks are done in the section of code that starts with IF @CheckUserDatabaseObjects = 1.
TOURSTOP04: Dealing with Version Differences
SQL Server has gotten more powerful over time, adding more diagnostic tables we can check out, but sometimes those queries will fail if run on an older version of SQL Server.
TOURSTOP04 is an example:
If you try to query the is_encrypted field on sys.databases in SQL 2000 and 2005, your query will fail because that field didn’t exist at the time. Therefore, we frame the whole thing in dynamic SQL.
TOURSTOP04 is a good example of several other things:
- SQL 2000 and 2005 aren’t supported anyway, but this code is still in there. The code used to work okay on 2000/2005, but we’ve given up on that now that those are no longer supported, and we’re using things that only exist on 2008.
- There are better ways to check versions, and not all the code is consistent in checking for it. In a perfect world, our code would be perfect. In practice…
- Our indenting sucks.
TOURSTOP05: Outputting the Results
After populating #BlitzResults for thousands of lines of checks, time to dump it out:
And that’s it. You get a nicely formatted list of issues with your server.
This means that writing a new check is as easy as writing an INSERT statement. Well, almost – it gets just a little bit trickier because people are allowed to declare checks they want to skip.
TOURSTOP06 and TOURSTOP07: Users Can Skip Checks
Every check in sp_Blitz has a unique ID – here’s the current list of sp_Blitz CheckIDs (you have to scroll right to see the CheckIDs) and it’s also available as a Markdown file inside the First Responder Kit zip file.
The unique checks are used to build tools atop the sp_Blitz output, and they’re also used to let people skip specific checks. To see it in action, TOURSTOP06 is just a little above TOURSTOP02:
There’s an IF NOT EXISTS statement that looks for a record in #SkipChecks, which is created and populated in TOURSTOP07:
When we first introduced that, people thought @SkipChecksDatabase by itself would skip all checks for that database, but that’s not how it works. @SkipChecksDatabase, @SkipChecksSchema, and @SkipChecksTable let you pass in a database, schema, and table where your own SkipChecks table lives.
Inside that table:
- If ServerName is populated, and DatabaseName & CheckID are null, then all checks are skipped for that server
- If DatabaseName is populated, and CheckID is null, then all checks are skipped for that database
- If CheckID is populated, but ServerName & DatabaseName is null, then that check is skipped everywhere
That’s the theory, anyway. We don’t get a lot of bug reports on that capability, so I’m not sure how many people are using it, but sp_Blitz uses it too.
TOURSTOP08: Skipping Checks for Amazon RDS
Amazon RDS for SQL Server is their hosted & managed flavor of SQL Server. It’s real SQL Server, but they’ve disabled some administrative capabilities, so there are some queries you can’t run.
To work around that, we simply skip the checks we can’t run in RDS:
This works really well because RDS can execute everything else in sp_Blitz. This approach doesn’t work for Azure SQL DB because DMV access is so incredibly hobbled – for example, sp_MSforeachdb is out the window.
So there you have it – your first guided tour to the sp_Blitz source code. I’ll do another post to lay out a few more intriguing areas, like how we output to tables & Markdown.