sp_Blitz, sp_BlitzCache, sp_BlitzFirst, sp_BlitzIndex, and other SQL Server scripts for health checks and performance tuning.
See the codeNavigation
You're a DBA, sysadmin, or developer who manages Microsoft SQL Servers. It's your fault if they're down or slow. These tools help you understand what's going on in your server.
To install, download the latest release ZIP, then run the SQL files in the master database. (You can use other databases if you prefer.)
The First Responder Kit runs on:
If you're stuck with versions of SQL Server that Microsoft no longer supports, like SQL Server 2008, check the Deprecated folder for older versions of the scripts which may work, depending on your versions and compatibility levels.
For SQL Server, to install all of the scripts at once, open Install-All-Scripts.sql in SSMS or Azure Data Studio, switch to the database where you want to install the procs, and run it. It will install the stored procedures if they don't already exist, or update them to the current version if they do exist.
For Azure SQL DB, use Install-Azure.sql, which will only install the procs that are compatible with Azure SQL DB.
If you hit an error when running Install-All-Scripts, it's likely because you're using an older version of SQL Server that Microsoft no longer supports. In that case, check out the Deprecated folder. That's where we keep old versions of the scripts around as a last-ditch effort - but really, if Microsoft won't support their own old stuff, you shouldn't try to do it either.
We recommend installing these stored procedures in the master database, but if you want to use another one, that's totally fine - they're all supported in any database - but just be aware that you can run into problems if you have these procs in multiple databases. You may not keep them all up to date, and you may hit an issue when you're running an older version.
There are a couple of Install-Core scripts included for legacy purposes, for folks with installers they've built. You can ignore those.
Everyone here is expected to abide by the Contributor Covenant Code of Conduct.
When you have questions about how the tools work, talk with the community in the #FirstResponderKit Slack channel. If you need a free invite, hit SQLslack.com. Be patient - it's staffed with volunteers who have day jobs, heh.
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 us!) 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.
Run sp_Blitz daily or weekly for an overall health check. Just run it from SQL Server Management Studio, and you'll get a prioritized list of issues on your server right now.
Note: sp_Blitz requires sp_ineachdb (also shipped in this repo) to be installed in the same database. Install-All-Scripts.sql installs both automatically, with sp_ineachdb placed first. If you're installing scripts individually, install sp_ineachdb.sql before sp_Blitz.sql, or sp_Blitz will fail at runtime on its per-database checks. See the sp_ineachdb section below for details.
Output columns include:
Commonly used parameters:
Azure SQL DB support: sp_Blitz runs on Azure SQL Database (SERVERPROPERTY('EngineEdition') = 5) and skips ~40 checks that rely on things Azure SQL DB does not expose: backup history in msdb, SQL Agent jobs and alerts, xp_* extended procs (drive space, error log, registry reads), sys.master_files, sp_validatelogins, cross-database DBCC commands, linked servers, and database mirroring / AGs. When sp_Blitz detects Azure SQL DB, a CheckID 223 "Some Checks Skipped" informational row is returned so you know coverage is reduced. Email output via @EmailRecipients (Database Mail is unavailable) and remote output via @OutputServerName (no linked servers) are also not supported on Azure SQL DB - sp_Blitz prints a not-supported message and continues instead of erroring. Managed Instance (EngineEdition = 8) and Amazon RDS are unaffected and continue to use their existing code paths.
Advanced tips:
In addition to the parameters common to many of the stored procedures, here are the ones specific to sp_Blitz:
EXEC sp_Blitz @OutputDatabaseName = 'DBAtools', @OutputSchemaName = 'dbo', @OutputTableName = 'BlitzResults';
Checks for the existence of a table DBAtools.dbo.BlitzResults, creates it if necessary, then adds the output of sp_Blitz into this table. This table is designed to support multiple outputs from multiple servers, so you can track your server's configuration history over time.
CREATE TABLE dbo.BlitzChecksToSkip (
ServerName NVARCHAR(128),
DatabaseName NVARCHAR(128),
CheckID INT
);
GO
INSERT INTO dbo.BlitzChecksToSkip (ServerName, DatabaseName, CheckID)
VALUES (NULL, 'SalesDB', 50)
sp_Blitz @SkipChecksDatabase = 'DBAtools', @SkipChecksSchema = 'dbo', @SkipChecksTable = 'BlitzChecksToSkip';
Checks for the existence of a table named Fred - just kidding, named DBAtools.dbo.BlitzChecksToSkip. The table needs at least the columns shown above (ServerName, DatabaseName, and CheckID). For each row:
sp_BlitzCache looks at your plan cache where SQL Server keeps track of which queries have run recently, and how much impact they've had on the server.
By default, it includes two result sets:
Output columns include:
By default, SQL Server caches the estimated plan only. If you want the most recent actual query plan, you can enable the database-level option for last query plan stats with the following statement, but that does come with a CPU performance overhead, especially on database servers that run thousands of batch requests per second.
ALTER DATABASE SCOPED CONFIGURATION SET LAST_QUERY_PLAN_STATS = ON;
The @SortOrder parameter lets you pick which top 10 queries you want to examine:
Other common parameters include:
dbadmin, dbmaintenance, and dbatools.In addition to the parameters common to many of the stored procedures, here are the ones specific to sp_BlitzCache:
When performance emergencies strike, this should be the first stored proc in the kit you run.
It takes a sample from a bunch of DMVs (wait stats, Perfmon counters, plan cache), waits 5 seconds, and then takes another sample. It examines the differences between the samples, and then gives you a prioritized list of things that might be causing performance issues right now. Examples include:
If no problems are found, it'll tell you that too. That's one of our favorite features because you can have your help desk team run sp_BlitzFirst and read the output to you over the phone. If no problems are found, you can keep right on drinking at the bar. (Ha! Just kidding, you'll still have to close out your tab, but at least you'll feel better about finishing that drink rather than trying to sober up.)
Common sp_BlitzFirst parameters include:
You can log sp_BlitzFirst performance data to tables by scheduling an Agent job to run sp_BlitzFirst every 15 minutes with these parameters populated:
All of the above OutputTableName parameters are optional: if you don't want to collect all of the stats, you don't have to. Keep in mind that the sp_BlitzCache results will get large, fast, because each execution plan is megabytes in size.
You can also log your own activities like tuning queries, adding indexes, or changing configuration settings. To do it, run sp_BlitzFirst with these parameters:
Optionally, you can also pass in:
SQL Server tracks your indexes: how big they are, how often they change, whether they're used to make queries go faster, and which indexes you should consider adding. The results columns are fairly self-explanatory.
By default, sp_BlitzIndex analyzes the indexes of the database you're in (your current context.)
Common parameters include:
sp_BlitzIndex focuses on mainstream index types. Other index types have varying amounts of support:
In addition to the parameters common to many of the stored procedures, here are the ones specific to sp_BlitzIndex:
Checks either the System Health session or a specific Extended Event session that captures deadlocks and parses out all the XML for you.
Parameters you can use:
Known issues:
This is like sp_who, except it goes into way, way, way more details.
It's designed for query tuners, so it includes things like memory grants, degrees of parallelism, and execution plans.
When the same query runs differently on two SQL Servers (fast on dev, slow on prod), the cause is almost always a difference in environment: stats, indexes, row counts, sniffed parameters, compat level, cardinality estimator version, hardware, MAXDOP, or live contention. sp_BlitzPlanCompare diffs two servers along every dimension that affects a specific query plan and returns a prioritized list of differences, the two plans side by side, and - when applicable - a copy/paste reproducer for parameter sniffing.
Installation: sp_BlitzPlanCompare ships in the
OptionalScriptsfolder and is not included inInstall-All-Scripts.sqlorInstall-Azure.sql. To use it, openOptionalScripts/sp_BlitzPlanCompare.sqlin SSMS, switch to the database where you want it installed (typicallymasterso it's callable from any DB viaEXEC master.dbo.sp_BlitzPlanCompare), and run the script. For linked-server mode, install it on both servers.
Plan identifiers — at least one required for modes 1 and 3 (mode 2 reads identity from the XML):
| Parameter | Type | Default | Purpose |
|---|---|---|---|
@QueryPlanHash | BINARY(8) | NULL | The query_plan_hash of a cached plan. Most specific identifier. |
@QueryHash | BINARY(8) | NULL | The query_hash (logical query fingerprint). Stable across servers; usually narrows to 1–few plans. When >1 you get a disambiguation result set to pick a @QueryPlanHash. |
@StoredProcName | NVARCHAR(400) | NULL | Proc name — bare (usp_Foo), schema-qualified (dbo.usp_Foo), or three-part ([db].[schema].[usp_Foo]). Resolved via OBJECT_ID(). Multi-statement procs typically return multiple plans; use the disambiguation result set to pick one. |
Narrower — must accompany an identifier (cannot stand alone):
| Parameter | Type | Default | Purpose |
|---|---|---|---|
@DatabaseName | SYSNAME | NULL | Scopes plan lookup (and @StoredProcName resolution) to this database. On Azure SQL DB you must be connected to this database — cross-DB OBJECT_ID() isn't supported. |
Comparison source (exactly one, or none for mode 1):
| Parameter | Type | Default | Purpose |
|---|---|---|---|
@CompareToXML | XML | NULL | Snapshot XML produced by a prior mode-1 run on the other server. Triggers mode 2. |
@LinkedServer | SYSNAME | NULL | Name of a configured linked server (with RPC OUT). Combine with a plan identifier for mode 3. |
Misc:
| Parameter | Type | Default | Purpose |
|---|---|---|---|
@Help | BIT | 0 | Prints usage and parameter docs and returns. |
@Debug | BIT | 0 | Prints the dynamic SQL used for plan lookup and linked-server invocation, plus per-DB iteration notes. |
| Mode | You supply | What it does | Output |
|---|---|---|---|
| 1. Emit | Any plan identifier(s) | Snapshots the local plan + its environment. | A single CallStack cell with a ready-to-run EXEC sp_BlitzPlanCompare @CompareToXML = N'...'; — paste into the other server. |
| 2. Compare from XML | @CompareToXML only | Resolves the local plan by query_hash from the snapshot, then diffs. | Full diff result set (below). |
| 3. Linked server | Plan identifier(s) + @LinkedServer | Calls sp_BlitzPlanCompare on the remote over RPC, shreds the returned snapshot, diffs locally. Requires the proc installed on both sides. | Full diff result set (below). |
/* 1. You know the plan hash exactly */
EXEC dbo.sp_BlitzPlanCompare @QueryPlanHash = 0xABCD1234567890EF;
/* 2. You only know the query hash (usually enough) */
EXEC dbo.sp_BlitzPlanCompare @QueryHash = 0x1234567890ABCDEF;
/* 3. You know the proc name */
EXEC dbo.sp_BlitzPlanCompare
@StoredProcName = 'dbo.usp_GetTopPosts',
@DatabaseName = 'StackOverflow';
/* Multi-statement proc: use the disambiguation result set to pick one statement */
EXEC dbo.sp_BlitzPlanCompare @StoredProcName = 'dbo.big_proc';
/* -> returns one row per cached plan with set_options + query_text_snippet.
Copy a query_plan_hash_text value and re-run with @QueryPlanHash. */
/* Step 1 — on the server where you noticed the slowness */
EXEC dbo.sp_BlitzPlanCompare @QueryPlanHash = 0xABCD1234567890EF;
You get back a single CallStack column — a complete EXEC dbo.sp_BlitzPlanCompare @CompareToXML = N'...'; with the snapshot XML already embedded and single quotes escaped. Click the cell, copy it, paste it into a query window connected to the OTHER server, hit F5. The comparison runs there and returns the prioritized diff plus both plans as clickable XML.
How cross-server plan resolution works: the snapshot carries the stable QueryHash attribute (not just the QueryPlanHash). When the other server runs the compare, it looks up its own cached plan by query_hash — because the whole point of comparing is that the plan on the other server is probably different. That divergence is the mystery you're investigating.
/* Needs RPC OUT and the proc installed on both sides. */
EXEC dbo.sp_BlitzPlanCompare @QueryPlanHash = 0xABCD1234567890EF,
@LinkedServer = 'OtherSrv';
The local side passes its query_hash (not query_plan_hash) to the remote — since plan hashes almost always differ, while query hashes are stable. The remote resolves its own matching plan and hands back its snapshot; the local side shreds it and emits the diff. If the two servers compiled different plans (the usual case when one is slow), both plans come back as clickable XML in the second result set.
First result set — the diff (one row per difference, ordered by Priority):
| Column | Description |
|---|---|
Priority | 0 = header metadata, 1 = plan-breaking / setup errors, 5 = operator row-count divergence (same-shape plans), 10 = parameter sniffing, 15 = CE drift / operator divergence (different-shape plans), 20 = major perf drivers (DOP, memory grant, hardware, wait stats), 25 = index quality, 30 = plan warnings / UDF time, 35 = live state, 55 = config drift, 100 = cosmetic / informational (PlanShape, versions), 200 = informational metadata echo. |
Category | Server, Hardware, SpConfigure, TraceFlag, Database, DatabaseScopedConfig, Object, Index, Statistics, RowCount, PlanAttribute, PlanWarning, PlanRuntime, PlanWait, Parameter, PlanShape, OperatorVariance, LiveState, ForcedPlan, Meta. |
Setting | Specific knob or metric within the category (e.g. max degree of parallelism, CompiledValue, CardinalityEstimationModelVersion, Reproducer). |
Object | Scoped identifier: db.schema.table, db.schema.table.index, NodeId N: OpName on ..., etc. |
LocalValue, RemoteValue | Side-by-side values. |
Finding | One-line explanation of the diff. |
URL | Link to more info. |
Details | Extra context - plan-source flags, shape-match verdict, operator metadata. |
CallStack | Typed XML — populated only on the Parameter.Reproducer row with a click-to-view reproducer containing two blocks of copy/paste-ready EXECs (one per server) that re-compile the proc/ad-hoc SQL with each side's sniffed parameter values. |
Second result set — the two plans (one row per server):
| Column | Description |
|---|---|
Server | Local or Remote. |
ServerName | @@SERVERNAME of the source. |
PlanSource | Actual (LAST_QUERY_PLAN_STATS), Cached estimated, (unknown). |
QueryPlan | Typed XML ShowPlanXML — click it in SSMS to open the plan diagram. |
@@SERVERNAME, edition, version, CPU count, scheduler count, physical memory, max server memory, committed target.DBCC TRACESTATUS(-1) (box SQL + MI only; silently skipped on Azure SQL DB).sys.database_scoped_configurations, including LEGACY_CARDINALITY_ESTIMATION, MAXDOP, LAST_QUERY_PLAN_STATS.sys.plan_guides.Parameter.Reproducer row is emitted whose CallStack column is a clickable XML cell with copy/paste-ready EXEC [db].[schema].[proc] @p = v blocks for each server. For ad-hoc parameterized SQL, the reproducer uses sp_executesql form instead.CardinalityEstimationModelVersion is exact-match regardless (70 vs 160 vs 170 all change optimizer behavior).LAST_QUERY_PLAN_STATS). Sub-25% deltas suppressed except for spill count.sys.query_store_wait_stats (not the plan XML — dm_exec_query_plan_stats doesn't populate <WaitStats>). Bucketed by wait_category_desc. Silently skipped when Query Store is off.PlanShape informational row always accompanies this analysis and tells you whether shapes match and which metric was used.Scope is limited to objects actually referenced in the plan — only databases, tables, indexes, and statistics that appear in the plan XML are compared.
@QueryPlanHash doesn't match any cached query_plan_hash on the local server, sp_BlitzPlanCompare automatically retries as query_hash. If exactly one plan matches, it uses that plan and prints a note explaining the fallback. If multiple plans match, you get the candidate query_plan_hash values and a RAISERROR asking you to pick one.@QueryPlanHash matches multiple cached plans (same hash, different compile contexts), the proc returns the matches with their set_options and a query text snippet so you can disambiguate, then RAISERRORs.sp_BlitzPlanCompare resolves plans via sys.dm_exec_query_plan_stats first (the actual plan with runtime memory grant, real spill warnings, and per-operator runtime counters) and falls back to sys.dm_exec_query_plan (the cached estimated plan) when the actual isn't available.
To get the actual plan:
ALTER DATABASE SCOPED CONFIGURATION SET LAST_QUERY_PLAN_STATS = ON;
Requires SQL Server 2019+ or Azure SQL DB and adds a small per-execution CPU overhead. The diff surfaces a PlanAttribute.PlanSource row when the two servers used different sources. When both sides are actual, OperatorVariance uses ActualRows and PlanRuntime rows (elapsed, CPU, memory grant, spill count) are populated. When either side is estimated-only, OperatorVariance falls back to EstimateRows.
SQL Server 2017+, Azure SQL DB, Managed Instance, Hyperscale, Amazon RDS.
RelOp/*/RelOp nesting - subquery RelOps nested inside predicate ScalarOperator trees may not get parented correctly, but the overall first-variance call is still accurate for conventional plans.The proc only reads from DMVs and catalog views - no writes, no DDL. On the local server (any mode), the caller needs:
VIEW SERVER STATE - for plan cache and OS DMVs (sys.dm_exec_query_stats, sys.dm_os_sys_info, sys.dm_exec_query_memory_grants, etc.).VIEW DATABASE STATE on each plan-referenced database - for sys.database_scoped_configurations, sys.dm_db_partition_stats, sys.dm_db_stats_properties, sys.query_store_plan, sys.plan_guides.VIEW ANY DEFINITION (or VIEW DEFINITION on the specific objects) - so sys.tables, sys.indexes, sys.index_columns, sys.columns, sys.stats return rows.CONNECT to each plan-referenced database - the cross-database iteration uses USE [db] inside dynamic SQL.EXECUTE on dbo.sp_BlitzPlanCompare.The simplest grant for most shops:
USE master;
GRANT VIEW SERVER STATE TO [YourLogin];
GRANT VIEW ANY DEFINITION TO [YourLogin];
GRANT EXECUTE ON dbo.sp_BlitzPlanCompare TO [YourLogin];
GO
/* Then in each plan-referenced database: */
USE [YourDatabase];
GRANT VIEW DATABASE STATE TO [YourUser];
GO
One gotcha: DBCC TRACESTATUS(-1) traditionally requires sysadmin (or ALTER SETTINGS on newer versions). The script wraps it in TRY/CATCH, so without the permission you don't get an error - the TraceFlag category just reports as unsupported and trace-flag differences won't be detected.
On Azure SQL DB: there's no VIEW SERVER STATE. Use the database-scoped equivalents (VIEW DATABASE STATE plus the Azure-specific permissions that go with it). Trace flags are unsupported on Azure SQL DB anyway, so no loss there.
For linked-server mode (mode 3): the remote login mapped via the linked server needs the same permissions on the remote server - because the remote is running the same code path (mode 1, emit XML). It also needs EXECUTE on dbo.sp_BlitzPlanCompare on the remote (the proc must be installed on both sides).
The linked server itself must be configured with RPC OUT enabled. Without it, EXEC LinkedServer.master.dbo.sp_BlitzPlanCompare returns "Server '...' is not configured for RPC."
/* Enable RPC and RPC OUT on an existing linked server */
EXEC sp_serveroption @server = 'OtherSrv', @optname = 'rpc', @optvalue = 'true';
EXEC sp_serveroption @server = 'OtherSrv', @optname = 'rpc out', @optvalue = 'true';
GO
/* Verify */
SELECT name, is_rpc_out_enabled
FROM sys.servers
WHERE name = 'OtherSrv';
If the linked-server call fails with error 8509 (Import of Microsoft Distributed Transaction Coordinator (MS DTC) transaction failed: 0x8004d00e(XACT_E_NOTRANSACTION)), the linked server is trying to promote the remote call to a distributed transaction and MSDTC isn't reachable. Turn off remote proc transaction promotion on the linked server:
EXEC master.dbo.sp_serveroption
@server = N'OtherSrv',
@optname = N'remote proc transaction promotion',
@optvalue = N'false';
sp_BlitzPlanCompare detects error 8509 and emits a Priority-1 Setup.DtcPromotionFailed row with this fix command inline.
The login mapping (sp_addlinkedsrvlogin) decides which remote login your local session executes as. Whoever that login is on the remote server needs the same VIEW SERVER STATE / VIEW DATABASE STATE / VIEW ANY DEFINITION / EXECUTE grants listed above.
When the server is on fire and you're about to restart it out of desperation, run sp_kill instead. It collects all running session data, recommends which queries to kill (with reasons), optionally executes the kills for you, frees their plan cache entries, and logs everything.
By default, sp_kill runs in read-only mode - it just shows you what's happening and suggests what to kill. You have to explicitly opt in to actually killing sessions.
/* Just show me what's running - no killing: */
EXEC sp_kill;
/* Kill all lead blockers: */
EXEC sp_kill @LeadBlockers = 'Y', @ExecuteKills = 'Y';
/* Kill a specific session: */
EXEC sp_kill @SPID = 55, @ExecuteKills = 'Y';
/* Kill sleeping sessions with open transactions idle for 5+ minutes: */
EXEC sp_kill @HasOpenTran = 'Y', @SPIDState = 'S',
@RequestsOlderThanSeconds = 300, @ExecuteKills = 'Y';
/* Show what we'd kill for a login, sorted by CPU: */
EXEC sp_kill @LoginName = 'DOMAIN\TroublesomeUser', @OrderBy = 'cpu';
Parameters for targeting sessions:
Safety features:
Output table logging:
Retrieves data from the output tables where you are storing your sp_BlitzFirst output.
Parameters include:
Example calls:
Get information for the last hour from all sp_BlitzFirst output tables
EXEC sp_BlitzAnalysis
@StartDate = NULL,
@EndDate = NULL,
@OutputDatabaseName = 'DBAtools',
@OutputSchemaName = 'dbo',
@OutputTableNameFileStats = N'BlitzFirst_FileStats',
@OutputTableNamePerfmonStats = N'BlitzFirst_PerfmonStats',
@OutputTableNameWaitStats = N'BlitzFirst_WaitStats',
@OutputTableNameBlitzCache = N'BlitzCache',
@OutputTableNameBlitzWho = N'BlitzWho';
Exclude specific tables e.g lets exclude PerfmonStats by setting to NULL, no lookup will occur against the table and a skipped message will appear in the resultset
EXEC sp_BlitzAnalysis
@StartDate = NULL,
@EndDate = NULL,
@OutputDatabaseName = 'DBAtools',
@OutputSchemaName = 'Blitz',
@OutputTableNameFileStats = N'BlitzFirst_FileStats',
@OutputTableNamePerfmonStats = NULL,
@OutputTableNameWaitStats = N'BlitzFirst_WaitStats',
@OutputTableNameBlitzCache = N'BlitzCache',
@OutputTableNameBlitzWho = N'BlitzWho';
Known issues: We are likely to be hitting some big tables here and some of these queries will require scans of the clustered indexes as there are no nonclustered indexes to cover the queries by default, keep this in mind if you are planning on running this in a production environment!
I have noticed that the Perfmon query can ask for a big memory grant so be mindful when including this table with large volumes of data:
SELECT
[ServerName]
, [CheckDate]
, [counter_name]
, [object_name]
, [instance_name]
, [cntr_value]
FROM [dbo].[BlitzFirst_PerfmonStats_Actuals]
WHERE CheckDate BETWEEN @FromDate AND @ToDate
ORDER BY
[CheckDate] ASC
, [counter_name] ASC;
Checks your backups and reports estimated RPO and RTO based on historical data in msdb, or a centralized location for [msdb].dbo.backupset.
Parameters include:
An example run of sp_BlitzBackups to push data looks like this:
EXEC sp_BlitzBackups @PushBackupHistoryToListener = 1, -- Turn it on!
@WriteBackupsToListenerName = 'AG_LISTENER_NAME', -- Name of AG Listener and Linked Server
@WriteBackupsToDatabaseName = 'FAKE_MSDB_NAME', -- Fake MSDB name you want to push to. Remember, can't be real MSDB.
@WriteBackupsLastHours = -24 -- Hours back in time you want to go
In an effort to not clog your servers up, we've taken some care in batching things as we move data. Inspired by Michael J. Swart's Take Care When Scripting Batches, we only move data in 10 minute intervals.
The reason behind that is, if you have 500 databases, and you're taking log backups every minute, you can have a lot of data to move. A 5000 row batch should move pretty quickly.
If you use Ola Hallengren's backup scripts, DatabaseRestore.sql helps you rapidly restore a database to the most recent point in time.
Parameters include:
For information about how this works, see Tara Kizer's white paper on Log Shipping 2.0 with Google Compute Engine.
sp_ineachdb is a drop-in replacement for the undocumented sp_MSforeachdb with saner filtering and error handling. Pass a command string using ? as a placeholder for the database name:
EXEC sp_ineachdb @command = N'SELECT DB_NAME() AS db, COUNT(*) AS tables FROM [?].sys.tables;';
Useful parameters:
@command - the T-SQL to run. ? (or whatever you set @replace_character to) gets replaced with the quoted database name.@database_list, @exclude_list - comma-separated lists of databases to include or exclude. Bracket-quote names that contain special characters.@name_pattern, @exclude_pattern - LIKE patterns applied to database names.@system_only, @user_only - limit to system DBs (master/model/msdb/tempdb/distribution) or user DBs.@is_read_only - defaults to 0, which skips read-only databases. This one surprises people, so read carefully:
0 (the default) - only databases that are not read-only. This is the safe default because most commands you'd run in each database make writeable changes, and those would fail in a read-only database.1 - only read-only databases.NULL - all databases, read-only or not. Pass this when you want a truly server-wide pass and your command is read-only, like SELECTs or DBCC CHECKDB.@state_desc - defaults to N'ONLINE', so offline, restoring, and recovering databases are skipped too. Pass NULL for all states.@recovery_model_desc, @compatibility_level, @is_auto_close_on, @is_auto_shrink_on, @is_broker_enabled, @is_query_store_on, @user_access - filter by database property. These all default to NULL, meaning no filtering.@is_ag_writeable_copy = 1 - skip Availability Group secondaries.@print_dbname, @print_command, @print_command_only, @select_dbname - diagnostics for debugging your command string without running it.Azure SQL DB forbids cross-database calls and 3-part object names, so "run in each database" collapses to "run in the current database" (Azure SQL DB sessions are bound to one user database anyway). sp_ineachdb detects Azure SQL DB via SERVERPROPERTY('EngineEdition') = 5 and adapts automatically:
@command via EXEC sys.sp_executesql instead of a 3-part dynamic call.sp_MSforeachdb-style patterns in @command so they don't have to be changed:
USE [?]; and USE ?; (with or without brackets or semicolons) are stripped - you can't change database context in Azure SQL DB.[?].schema.object and ?.schema.object are collapsed to schema.object, turning 3-part names into 2-part names.This means callers that already work with sp_MSforeachdb on box SQL (like sp_Blitz internals) can call sp_ineachdb on Azure SQL DB and Just Work.
Things to look out for on Azure SQL DB:
PRINT 'See [?].sys.tables' will have [?]. stripped out - avoid putting the placeholder inside quoted strings. (This caveat also applies to box SQL, since @command text substitution happens everywhere.)USE [?]; (double space) or [?] . sys . tables (spaces between name parts) won't be rewritten. Stick to the canonical USE [?]; and [?].sys.tables forms.EngineEdition = 8 and supports cross-database calls, so it uses the same code path as box SQL.@name_pattern, @user_only, property filters, etc. can still exclude it. If nothing matches, you get the normal No databases to process. message.sp_BlitzUpdate downloads a First Responder Kit file straight from GitHub and installs it in the current database, without sqlcmd, PowerShell, or RDP. It's for lab and dev servers you want to keep current with one command:
USE master;
GO
EXEC dbo.sp_BlitzUpdate;
That grabs Install-All-Scripts.sql from the main branch and runs it, batch by batch.
Installation: sp_BlitzUpdate ships in the
OptionalScriptsfolder and is not included inInstall-All-Scripts.sqlorInstall-Azure.sql. OpenOptionalScripts/sp_BlitzUpdate.sqlin SSMS, switch to the database where you want it installed (typicallymaster), and run it once. After that, call it whenever you want a refresh.
sys.sp_invoke_external_rest_endpoint, which doesn't exist in earlier versions. The script refuses to install on older builds, and the proc raises an error at runtime if it somehow got installed anyway.sp_configure 'external rest endpoint enabled', 1; RECONFIGURE;Msg 31612: Connections to the domain api.github.com are not allowed.sys.sp_invoke_external_rest_endpoint.| Parameter | Type | Default | Purpose |
|---|---|---|---|
@Branch | NVARCHAR(128) | N'main' | Branch or tag to pull from. Date-based release tags work too, like N'20260407'. |
@Repository | NVARCHAR(128) | N'BrentOzarULTD/SQL-Server-First-Responder-Kit' | owner/repo on GitHub. Point it at your fork if you maintain one. |
@FileName | NVARCHAR(256) | N'Install-All-Scripts.sql' | Path to the file inside the repo. Pass N'sp_Blitz.sql' (etc.) to update one proc at a time. |
@WhatIf | BIT | 0 | 1 fetches and parses the file, prints how many batches would run, and exits without installing anything. |
@Debug | TINYINT | 0 | 1 prints per-batch length and progress. |
@Help | TINYINT | 0 | 1 prints usage info and returns. |
/* Pinned to a release tag, which is what you want for reproducible lab boxes: */
EXEC dbo.sp_BlitzUpdate @Branch = N'20260407';
/* Update a single proc: */
EXEC dbo.sp_BlitzUpdate @FileName = N'sp_Blitz.sql';
/* Preview without installing: */
EXEC dbo.sp_BlitzUpdate @WhatIf = 1, @Debug = 1;
/* From a fork: */
EXEC dbo.sp_BlitzUpdate @Repository = N'yourname/SQL-Server-First-Responder-Kit', @Branch = N'dev';
@Branch instead of leaving it on main when you care about what you're getting.USE master; first, matching the regular FRK install workflow.To check versions of any of the stored procedures, use their output parameters for Version and VersionDate like this:
DECLARE @VersionOutput VARCHAR(30), @VersionDateOutput DATETIME;
EXEC sp_Blitz
@Version = @VersionOutput OUTPUT,
@VersionDate = @VersionDateOutput OUTPUT,
@VersionCheckMode = 1;
SELECT
@VersionOutput AS Version,
@VersionDateOutput AS VersionDate;
644 followers · starred Apr 2019
119 followers · starred Aug 2016
24 followers · starred Jun 2025
144 followers · starred Jul 2019
sp_Blitz, sp_BlitzCache, sp_BlitzFirst, sp_BlitzIndex, and other SQL Server scripts for health checks and performance tuning.
See the codeNavigation
You're a DBA, sysadmin, or developer who manages Microsoft SQL Servers. It's your fault if they're down or slow. These tools help you understand what's going on in your server.
To install, download the latest release ZIP, then run the SQL files in the master database. (You can use other databases if you prefer.)
The First Responder Kit runs on:
If you're stuck with versions of SQL Server that Microsoft no longer supports, like SQL Server 2008, check the Deprecated folder for older versions of the scripts which may work, depending on your versions and compatibility levels.
For SQL Server, to install all of the scripts at once, open Install-All-Scripts.sql in SSMS or Azure Data Studio, switch to the database where you want to install the procs, and run it. It will install the stored procedures if they don't already exist, or update them to the current version if they do exist.
For Azure SQL DB, use Install-Azure.sql, which will only install the procs that are compatible with Azure SQL DB.
If you hit an error when running Install-All-Scripts, it's likely because you're using an older version of SQL Server that Microsoft no longer supports. In that case, check out the Deprecated folder. That's where we keep old versions of the scripts around as a last-ditch effort - but really, if Microsoft won't support their own old stuff, you shouldn't try to do it either.
We recommend installing these stored procedures in the master database, but if you want to use another one, that's totally fine - they're all supported in any database - but just be aware that you can run into problems if you have these procs in multiple databases. You may not keep them all up to date, and you may hit an issue when you're running an older version.
There are a couple of Install-Core scripts included for legacy purposes, for folks with installers they've built. You can ignore those.
Everyone here is expected to abide by the Contributor Covenant Code of Conduct.
When you have questions about how the tools work, talk with the community in the #FirstResponderKit Slack channel. If you need a free invite, hit SQLslack.com. Be patient - it's staffed with volunteers who have day jobs, heh.
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 us!) 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.
Run sp_Blitz daily or weekly for an overall health check. Just run it from SQL Server Management Studio, and you'll get a prioritized list of issues on your server right now.
Note: sp_Blitz requires sp_ineachdb (also shipped in this repo) to be installed in the same database. Install-All-Scripts.sql installs both automatically, with sp_ineachdb placed first. If you're installing scripts individually, install sp_ineachdb.sql before sp_Blitz.sql, or sp_Blitz will fail at runtime on its per-database checks. See the sp_ineachdb section below for details.
Output columns include:
Commonly used parameters:
Azure SQL DB support: sp_Blitz runs on Azure SQL Database (SERVERPROPERTY('EngineEdition') = 5) and skips ~40 checks that rely on things Azure SQL DB does not expose: backup history in msdb, SQL Agent jobs and alerts, xp_* extended procs (drive space, error log, registry reads), sys.master_files, sp_validatelogins, cross-database DBCC commands, linked servers, and database mirroring / AGs. When sp_Blitz detects Azure SQL DB, a CheckID 223 "Some Checks Skipped" informational row is returned so you know coverage is reduced. Email output via @EmailRecipients (Database Mail is unavailable) and remote output via @OutputServerName (no linked servers) are also not supported on Azure SQL DB - sp_Blitz prints a not-supported message and continues instead of erroring. Managed Instance (EngineEdition = 8) and Amazon RDS are unaffected and continue to use their existing code paths.
Advanced tips:
In addition to the parameters common to many of the stored procedures, here are the ones specific to sp_Blitz:
EXEC sp_Blitz @OutputDatabaseName = 'DBAtools', @OutputSchemaName = 'dbo', @OutputTableName = 'BlitzResults';
Checks for the existence of a table DBAtools.dbo.BlitzResults, creates it if necessary, then adds the output of sp_Blitz into this table. This table is designed to support multiple outputs from multiple servers, so you can track your server's configuration history over time.
CREATE TABLE dbo.BlitzChecksToSkip (
ServerName NVARCHAR(128),
DatabaseName NVARCHAR(128),
CheckID INT
);
GO
INSERT INTO dbo.BlitzChecksToSkip (ServerName, DatabaseName, CheckID)
VALUES (NULL, 'SalesDB', 50)
sp_Blitz @SkipChecksDatabase = 'DBAtools', @SkipChecksSchema = 'dbo', @SkipChecksTable = 'BlitzChecksToSkip';
Checks for the existence of a table named Fred - just kidding, named DBAtools.dbo.BlitzChecksToSkip. The table needs at least the columns shown above (ServerName, DatabaseName, and CheckID). For each row:
sp_BlitzCache looks at your plan cache where SQL Server keeps track of which queries have run recently, and how much impact they've had on the server.
By default, it includes two result sets:
Output columns include:
By default, SQL Server caches the estimated plan only. If you want the most recent actual query plan, you can enable the database-level option for last query plan stats with the following statement, but that does come with a CPU performance overhead, especially on database servers that run thousands of batch requests per second.
ALTER DATABASE SCOPED CONFIGURATION SET LAST_QUERY_PLAN_STATS = ON;
The @SortOrder parameter lets you pick which top 10 queries you want to examine:
Other common parameters include:
dbadmin, dbmaintenance, and dbatools.In addition to the parameters common to many of the stored procedures, here are the ones specific to sp_BlitzCache:
When performance emergencies strike, this should be the first stored proc in the kit you run.
It takes a sample from a bunch of DMVs (wait stats, Perfmon counters, plan cache), waits 5 seconds, and then takes another sample. It examines the differences between the samples, and then gives you a prioritized list of things that might be causing performance issues right now. Examples include:
If no problems are found, it'll tell you that too. That's one of our favorite features because you can have your help desk team run sp_BlitzFirst and read the output to you over the phone. If no problems are found, you can keep right on drinking at the bar. (Ha! Just kidding, you'll still have to close out your tab, but at least you'll feel better about finishing that drink rather than trying to sober up.)
Common sp_BlitzFirst parameters include:
You can log sp_BlitzFirst performance data to tables by scheduling an Agent job to run sp_BlitzFirst every 15 minutes with these parameters populated:
All of the above OutputTableName parameters are optional: if you don't want to collect all of the stats, you don't have to. Keep in mind that the sp_BlitzCache results will get large, fast, because each execution plan is megabytes in size.
You can also log your own activities like tuning queries, adding indexes, or changing configuration settings. To do it, run sp_BlitzFirst with these parameters:
Optionally, you can also pass in:
SQL Server tracks your indexes: how big they are, how often they change, whether they're used to make queries go faster, and which indexes you should consider adding. The results columns are fairly self-explanatory.
By default, sp_BlitzIndex analyzes the indexes of the database you're in (your current context.)
Common parameters include:
sp_BlitzIndex focuses on mainstream index types. Other index types have varying amounts of support:
In addition to the parameters common to many of the stored procedures, here are the ones specific to sp_BlitzIndex:
Checks either the System Health session or a specific Extended Event session that captures deadlocks and parses out all the XML for you.
Parameters you can use:
Known issues:
This is like sp_who, except it goes into way, way, way more details.
It's designed for query tuners, so it includes things like memory grants, degrees of parallelism, and execution plans.
When the same query runs differently on two SQL Servers (fast on dev, slow on prod), the cause is almost always a difference in environment: stats, indexes, row counts, sniffed parameters, compat level, cardinality estimator version, hardware, MAXDOP, or live contention. sp_BlitzPlanCompare diffs two servers along every dimension that affects a specific query plan and returns a prioritized list of differences, the two plans side by side, and - when applicable - a copy/paste reproducer for parameter sniffing.
Installation: sp_BlitzPlanCompare ships in the
OptionalScriptsfolder and is not included inInstall-All-Scripts.sqlorInstall-Azure.sql. To use it, openOptionalScripts/sp_BlitzPlanCompare.sqlin SSMS, switch to the database where you want it installed (typicallymasterso it's callable from any DB viaEXEC master.dbo.sp_BlitzPlanCompare), and run the script. For linked-server mode, install it on both servers.
Plan identifiers — at least one required for modes 1 and 3 (mode 2 reads identity from the XML):
| Parameter | Type | Default | Purpose |
|---|---|---|---|
@QueryPlanHash | BINARY(8) | NULL | The query_plan_hash of a cached plan. Most specific identifier. |
@QueryHash | BINARY(8) | NULL | The query_hash (logical query fingerprint). Stable across servers; usually narrows to 1–few plans. When >1 you get a disambiguation result set to pick a @QueryPlanHash. |
@StoredProcName | NVARCHAR(400) | NULL | Proc name — bare (usp_Foo), schema-qualified (dbo.usp_Foo), or three-part ([db].[schema].[usp_Foo]). Resolved via OBJECT_ID(). Multi-statement procs typically return multiple plans; use the disambiguation result set to pick one. |
Narrower — must accompany an identifier (cannot stand alone):
| Parameter | Type | Default | Purpose |
|---|---|---|---|
@DatabaseName | SYSNAME | NULL | Scopes plan lookup (and @StoredProcName resolution) to this database. On Azure SQL DB you must be connected to this database — cross-DB OBJECT_ID() isn't supported. |
Comparison source (exactly one, or none for mode 1):
| Parameter | Type | Default | Purpose |
|---|---|---|---|
@CompareToXML | XML | NULL | Snapshot XML produced by a prior mode-1 run on the other server. Triggers mode 2. |
@LinkedServer | SYSNAME | NULL | Name of a configured linked server (with RPC OUT). Combine with a plan identifier for mode 3. |
Misc:
| Parameter | Type | Default | Purpose |
|---|---|---|---|
@Help | BIT | 0 | Prints usage and parameter docs and returns. |
@Debug | BIT | 0 | Prints the dynamic SQL used for plan lookup and linked-server invocation, plus per-DB iteration notes. |
| Mode | You supply | What it does | Output |
|---|---|---|---|
| 1. Emit | Any plan identifier(s) | Snapshots the local plan + its environment. | A single CallStack cell with a ready-to-run EXEC sp_BlitzPlanCompare @CompareToXML = N'...'; — paste into the other server. |
| 2. Compare from XML | @CompareToXML only | Resolves the local plan by query_hash from the snapshot, then diffs. | Full diff result set (below). |
| 3. Linked server | Plan identifier(s) + @LinkedServer | Calls sp_BlitzPlanCompare on the remote over RPC, shreds the returned snapshot, diffs locally. Requires the proc installed on both sides. | Full diff result set (below). |
/* 1. You know the plan hash exactly */
EXEC dbo.sp_BlitzPlanCompare @QueryPlanHash = 0xABCD1234567890EF;
/* 2. You only know the query hash (usually enough) */
EXEC dbo.sp_BlitzPlanCompare @QueryHash = 0x1234567890ABCDEF;
/* 3. You know the proc name */
EXEC dbo.sp_BlitzPlanCompare
@StoredProcName = 'dbo.usp_GetTopPosts',
@DatabaseName = 'StackOverflow';
/* Multi-statement proc: use the disambiguation result set to pick one statement */
EXEC dbo.sp_BlitzPlanCompare @StoredProcName = 'dbo.big_proc';
/* -> returns one row per cached plan with set_options + query_text_snippet.
Copy a query_plan_hash_text value and re-run with @QueryPlanHash. */
/* Step 1 — on the server where you noticed the slowness */
EXEC dbo.sp_BlitzPlanCompare @QueryPlanHash = 0xABCD1234567890EF;
You get back a single CallStack column — a complete EXEC dbo.sp_BlitzPlanCompare @CompareToXML = N'...'; with the snapshot XML already embedded and single quotes escaped. Click the cell, copy it, paste it into a query window connected to the OTHER server, hit F5. The comparison runs there and returns the prioritized diff plus both plans as clickable XML.
How cross-server plan resolution works: the snapshot carries the stable QueryHash attribute (not just the QueryPlanHash). When the other server runs the compare, it looks up its own cached plan by query_hash — because the whole point of comparing is that the plan on the other server is probably different. That divergence is the mystery you're investigating.
/* Needs RPC OUT and the proc installed on both sides. */
EXEC dbo.sp_BlitzPlanCompare @QueryPlanHash = 0xABCD1234567890EF,
@LinkedServer = 'OtherSrv';
The local side passes its query_hash (not query_plan_hash) to the remote — since plan hashes almost always differ, while query hashes are stable. The remote resolves its own matching plan and hands back its snapshot; the local side shreds it and emits the diff. If the two servers compiled different plans (the usual case when one is slow), both plans come back as clickable XML in the second result set.
First result set — the diff (one row per difference, ordered by Priority):
| Column | Description |
|---|---|
Priority | 0 = header metadata, 1 = plan-breaking / setup errors, 5 = operator row-count divergence (same-shape plans), 10 = parameter sniffing, 15 = CE drift / operator divergence (different-shape plans), 20 = major perf drivers (DOP, memory grant, hardware, wait stats), 25 = index quality, 30 = plan warnings / UDF time, 35 = live state, 55 = config drift, 100 = cosmetic / informational (PlanShape, versions), 200 = informational metadata echo. |
Category | Server, Hardware, SpConfigure, TraceFlag, Database, DatabaseScopedConfig, Object, Index, Statistics, RowCount, PlanAttribute, PlanWarning, PlanRuntime, PlanWait, Parameter, PlanShape, OperatorVariance, LiveState, ForcedPlan, Meta. |
Setting | Specific knob or metric within the category (e.g. max degree of parallelism, CompiledValue, CardinalityEstimationModelVersion, Reproducer). |
Object | Scoped identifier: db.schema.table, db.schema.table.index, NodeId N: OpName on ..., etc. |
LocalValue, RemoteValue | Side-by-side values. |
Finding | One-line explanation of the diff. |
URL | Link to more info. |
Details | Extra context - plan-source flags, shape-match verdict, operator metadata. |
CallStack | Typed XML — populated only on the Parameter.Reproducer row with a click-to-view reproducer containing two blocks of copy/paste-ready EXECs (one per server) that re-compile the proc/ad-hoc SQL with each side's sniffed parameter values. |
Second result set — the two plans (one row per server):
| Column | Description |
|---|---|
Server | Local or Remote. |
ServerName | @@SERVERNAME of the source. |
PlanSource | Actual (LAST_QUERY_PLAN_STATS), Cached estimated, (unknown). |
QueryPlan | Typed XML ShowPlanXML — click it in SSMS to open the plan diagram. |
@@SERVERNAME, edition, version, CPU count, scheduler count, physical memory, max server memory, committed target.DBCC TRACESTATUS(-1) (box SQL + MI only; silently skipped on Azure SQL DB).sys.database_scoped_configurations, including LEGACY_CARDINALITY_ESTIMATION, MAXDOP, LAST_QUERY_PLAN_STATS.sys.plan_guides.Parameter.Reproducer row is emitted whose CallStack column is a clickable XML cell with copy/paste-ready EXEC [db].[schema].[proc] @p = v blocks for each server. For ad-hoc parameterized SQL, the reproducer uses sp_executesql form instead.CardinalityEstimationModelVersion is exact-match regardless (70 vs 160 vs 170 all change optimizer behavior).LAST_QUERY_PLAN_STATS). Sub-25% deltas suppressed except for spill count.sys.query_store_wait_stats (not the plan XML — dm_exec_query_plan_stats doesn't populate <WaitStats>). Bucketed by wait_category_desc. Silently skipped when Query Store is off.PlanShape informational row always accompanies this analysis and tells you whether shapes match and which metric was used.Scope is limited to objects actually referenced in the plan — only databases, tables, indexes, and statistics that appear in the plan XML are compared.
@QueryPlanHash doesn't match any cached query_plan_hash on the local server, sp_BlitzPlanCompare automatically retries as query_hash. If exactly one plan matches, it uses that plan and prints a note explaining the fallback. If multiple plans match, you get the candidate query_plan_hash values and a RAISERROR asking you to pick one.@QueryPlanHash matches multiple cached plans (same hash, different compile contexts), the proc returns the matches with their set_options and a query text snippet so you can disambiguate, then RAISERRORs.sp_BlitzPlanCompare resolves plans via sys.dm_exec_query_plan_stats first (the actual plan with runtime memory grant, real spill warnings, and per-operator runtime counters) and falls back to sys.dm_exec_query_plan (the cached estimated plan) when the actual isn't available.
To get the actual plan:
ALTER DATABASE SCOPED CONFIGURATION SET LAST_QUERY_PLAN_STATS = ON;
Requires SQL Server 2019+ or Azure SQL DB and adds a small per-execution CPU overhead. The diff surfaces a PlanAttribute.PlanSource row when the two servers used different sources. When both sides are actual, OperatorVariance uses ActualRows and PlanRuntime rows (elapsed, CPU, memory grant, spill count) are populated. When either side is estimated-only, OperatorVariance falls back to EstimateRows.
SQL Server 2017+, Azure SQL DB, Managed Instance, Hyperscale, Amazon RDS.
RelOp/*/RelOp nesting - subquery RelOps nested inside predicate ScalarOperator trees may not get parented correctly, but the overall first-variance call is still accurate for conventional plans.The proc only reads from DMVs and catalog views - no writes, no DDL. On the local server (any mode), the caller needs:
VIEW SERVER STATE - for plan cache and OS DMVs (sys.dm_exec_query_stats, sys.dm_os_sys_info, sys.dm_exec_query_memory_grants, etc.).VIEW DATABASE STATE on each plan-referenced database - for sys.database_scoped_configurations, sys.dm_db_partition_stats, sys.dm_db_stats_properties, sys.query_store_plan, sys.plan_guides.VIEW ANY DEFINITION (or VIEW DEFINITION on the specific objects) - so sys.tables, sys.indexes, sys.index_columns, sys.columns, sys.stats return rows.CONNECT to each plan-referenced database - the cross-database iteration uses USE [db] inside dynamic SQL.EXECUTE on dbo.sp_BlitzPlanCompare.The simplest grant for most shops:
USE master;
GRANT VIEW SERVER STATE TO [YourLogin];
GRANT VIEW ANY DEFINITION TO [YourLogin];
GRANT EXECUTE ON dbo.sp_BlitzPlanCompare TO [YourLogin];
GO
/* Then in each plan-referenced database: */
USE [YourDatabase];
GRANT VIEW DATABASE STATE TO [YourUser];
GO
One gotcha: DBCC TRACESTATUS(-1) traditionally requires sysadmin (or ALTER SETTINGS on newer versions). The script wraps it in TRY/CATCH, so without the permission you don't get an error - the TraceFlag category just reports as unsupported and trace-flag differences won't be detected.
On Azure SQL DB: there's no VIEW SERVER STATE. Use the database-scoped equivalents (VIEW DATABASE STATE plus the Azure-specific permissions that go with it). Trace flags are unsupported on Azure SQL DB anyway, so no loss there.
For linked-server mode (mode 3): the remote login mapped via the linked server needs the same permissions on the remote server - because the remote is running the same code path (mode 1, emit XML). It also needs EXECUTE on dbo.sp_BlitzPlanCompare on the remote (the proc must be installed on both sides).
The linked server itself must be configured with RPC OUT enabled. Without it, EXEC LinkedServer.master.dbo.sp_BlitzPlanCompare returns "Server '...' is not configured for RPC."
/* Enable RPC and RPC OUT on an existing linked server */
EXEC sp_serveroption @server = 'OtherSrv', @optname = 'rpc', @optvalue = 'true';
EXEC sp_serveroption @server = 'OtherSrv', @optname = 'rpc out', @optvalue = 'true';
GO
/* Verify */
SELECT name, is_rpc_out_enabled
FROM sys.servers
WHERE name = 'OtherSrv';
If the linked-server call fails with error 8509 (Import of Microsoft Distributed Transaction Coordinator (MS DTC) transaction failed: 0x8004d00e(XACT_E_NOTRANSACTION)), the linked server is trying to promote the remote call to a distributed transaction and MSDTC isn't reachable. Turn off remote proc transaction promotion on the linked server:
EXEC master.dbo.sp_serveroption
@server = N'OtherSrv',
@optname = N'remote proc transaction promotion',
@optvalue = N'false';
sp_BlitzPlanCompare detects error 8509 and emits a Priority-1 Setup.DtcPromotionFailed row with this fix command inline.
The login mapping (sp_addlinkedsrvlogin) decides which remote login your local session executes as. Whoever that login is on the remote server needs the same VIEW SERVER STATE / VIEW DATABASE STATE / VIEW ANY DEFINITION / EXECUTE grants listed above.
When the server is on fire and you're about to restart it out of desperation, run sp_kill instead. It collects all running session data, recommends which queries to kill (with reasons), optionally executes the kills for you, frees their plan cache entries, and logs everything.
By default, sp_kill runs in read-only mode - it just shows you what's happening and suggests what to kill. You have to explicitly opt in to actually killing sessions.
/* Just show me what's running - no killing: */
EXEC sp_kill;
/* Kill all lead blockers: */
EXEC sp_kill @LeadBlockers = 'Y', @ExecuteKills = 'Y';
/* Kill a specific session: */
EXEC sp_kill @SPID = 55, @ExecuteKills = 'Y';
/* Kill sleeping sessions with open transactions idle for 5+ minutes: */
EXEC sp_kill @HasOpenTran = 'Y', @SPIDState = 'S',
@RequestsOlderThanSeconds = 300, @ExecuteKills = 'Y';
/* Show what we'd kill for a login, sorted by CPU: */
EXEC sp_kill @LoginName = 'DOMAIN\TroublesomeUser', @OrderBy = 'cpu';
Parameters for targeting sessions:
Safety features:
Output table logging:
Retrieves data from the output tables where you are storing your sp_BlitzFirst output.
Parameters include:
Example calls:
Get information for the last hour from all sp_BlitzFirst output tables
EXEC sp_BlitzAnalysis
@StartDate = NULL,
@EndDate = NULL,
@OutputDatabaseName = 'DBAtools',
@OutputSchemaName = 'dbo',
@OutputTableNameFileStats = N'BlitzFirst_FileStats',
@OutputTableNamePerfmonStats = N'BlitzFirst_PerfmonStats',
@OutputTableNameWaitStats = N'BlitzFirst_WaitStats',
@OutputTableNameBlitzCache = N'BlitzCache',
@OutputTableNameBlitzWho = N'BlitzWho';
Exclude specific tables e.g lets exclude PerfmonStats by setting to NULL, no lookup will occur against the table and a skipped message will appear in the resultset
EXEC sp_BlitzAnalysis
@StartDate = NULL,
@EndDate = NULL,
@OutputDatabaseName = 'DBAtools',
@OutputSchemaName = 'Blitz',
@OutputTableNameFileStats = N'BlitzFirst_FileStats',
@OutputTableNamePerfmonStats = NULL,
@OutputTableNameWaitStats = N'BlitzFirst_WaitStats',
@OutputTableNameBlitzCache = N'BlitzCache',
@OutputTableNameBlitzWho = N'BlitzWho';
Known issues: We are likely to be hitting some big tables here and some of these queries will require scans of the clustered indexes as there are no nonclustered indexes to cover the queries by default, keep this in mind if you are planning on running this in a production environment!
I have noticed that the Perfmon query can ask for a big memory grant so be mindful when including this table with large volumes of data:
SELECT
[ServerName]
, [CheckDate]
, [counter_name]
, [object_name]
, [instance_name]
, [cntr_value]
FROM [dbo].[BlitzFirst_PerfmonStats_Actuals]
WHERE CheckDate BETWEEN @FromDate AND @ToDate
ORDER BY
[CheckDate] ASC
, [counter_name] ASC;
Checks your backups and reports estimated RPO and RTO based on historical data in msdb, or a centralized location for [msdb].dbo.backupset.
Parameters include:
An example run of sp_BlitzBackups to push data looks like this:
EXEC sp_BlitzBackups @PushBackupHistoryToListener = 1, -- Turn it on!
@WriteBackupsToListenerName = 'AG_LISTENER_NAME', -- Name of AG Listener and Linked Server
@WriteBackupsToDatabaseName = 'FAKE_MSDB_NAME', -- Fake MSDB name you want to push to. Remember, can't be real MSDB.
@WriteBackupsLastHours = -24 -- Hours back in time you want to go
In an effort to not clog your servers up, we've taken some care in batching things as we move data. Inspired by Michael J. Swart's Take Care When Scripting Batches, we only move data in 10 minute intervals.
The reason behind that is, if you have 500 databases, and you're taking log backups every minute, you can have a lot of data to move. A 5000 row batch should move pretty quickly.
If you use Ola Hallengren's backup scripts, DatabaseRestore.sql helps you rapidly restore a database to the most recent point in time.
Parameters include:
For information about how this works, see Tara Kizer's white paper on Log Shipping 2.0 with Google Compute Engine.
sp_ineachdb is a drop-in replacement for the undocumented sp_MSforeachdb with saner filtering and error handling. Pass a command string using ? as a placeholder for the database name:
EXEC sp_ineachdb @command = N'SELECT DB_NAME() AS db, COUNT(*) AS tables FROM [?].sys.tables;';
Useful parameters:
@command - the T-SQL to run. ? (or whatever you set @replace_character to) gets replaced with the quoted database name.@database_list, @exclude_list - comma-separated lists of databases to include or exclude. Bracket-quote names that contain special characters.@name_pattern, @exclude_pattern - LIKE patterns applied to database names.@system_only, @user_only - limit to system DBs (master/model/msdb/tempdb/distribution) or user DBs.@is_read_only - defaults to 0, which skips read-only databases. This one surprises people, so read carefully:
0 (the default) - only databases that are not read-only. This is the safe default because most commands you'd run in each database make writeable changes, and those would fail in a read-only database.1 - only read-only databases.NULL - all databases, read-only or not. Pass this when you want a truly server-wide pass and your command is read-only, like SELECTs or DBCC CHECKDB.@state_desc - defaults to N'ONLINE', so offline, restoring, and recovering databases are skipped too. Pass NULL for all states.@recovery_model_desc, @compatibility_level, @is_auto_close_on, @is_auto_shrink_on, @is_broker_enabled, @is_query_store_on, @user_access - filter by database property. These all default to NULL, meaning no filtering.@is_ag_writeable_copy = 1 - skip Availability Group secondaries.@print_dbname, @print_command, @print_command_only, @select_dbname - diagnostics for debugging your command string without running it.Azure SQL DB forbids cross-database calls and 3-part object names, so "run in each database" collapses to "run in the current database" (Azure SQL DB sessions are bound to one user database anyway). sp_ineachdb detects Azure SQL DB via SERVERPROPERTY('EngineEdition') = 5 and adapts automatically:
@command via EXEC sys.sp_executesql instead of a 3-part dynamic call.sp_MSforeachdb-style patterns in @command so they don't have to be changed:
USE [?]; and USE ?; (with or without brackets or semicolons) are stripped - you can't change database context in Azure SQL DB.[?].schema.object and ?.schema.object are collapsed to schema.object, turning 3-part names into 2-part names.This means callers that already work with sp_MSforeachdb on box SQL (like sp_Blitz internals) can call sp_ineachdb on Azure SQL DB and Just Work.
Things to look out for on Azure SQL DB:
PRINT 'See [?].sys.tables' will have [?]. stripped out - avoid putting the placeholder inside quoted strings. (This caveat also applies to box SQL, since @command text substitution happens everywhere.)USE [?]; (double space) or [?] . sys . tables (spaces between name parts) won't be rewritten. Stick to the canonical USE [?]; and [?].sys.tables forms.EngineEdition = 8 and supports cross-database calls, so it uses the same code path as box SQL.@name_pattern, @user_only, property filters, etc. can still exclude it. If nothing matches, you get the normal No databases to process. message.sp_BlitzUpdate downloads a First Responder Kit file straight from GitHub and installs it in the current database, without sqlcmd, PowerShell, or RDP. It's for lab and dev servers you want to keep current with one command:
USE master;
GO
EXEC dbo.sp_BlitzUpdate;
That grabs Install-All-Scripts.sql from the main branch and runs it, batch by batch.
Installation: sp_BlitzUpdate ships in the
OptionalScriptsfolder and is not included inInstall-All-Scripts.sqlorInstall-Azure.sql. OpenOptionalScripts/sp_BlitzUpdate.sqlin SSMS, switch to the database where you want it installed (typicallymaster), and run it once. After that, call it whenever you want a refresh.
sys.sp_invoke_external_rest_endpoint, which doesn't exist in earlier versions. The script refuses to install on older builds, and the proc raises an error at runtime if it somehow got installed anyway.sp_configure 'external rest endpoint enabled', 1; RECONFIGURE;Msg 31612: Connections to the domain api.github.com are not allowed.sys.sp_invoke_external_rest_endpoint.| Parameter | Type | Default | Purpose |
|---|---|---|---|
@Branch | NVARCHAR(128) | N'main' | Branch or tag to pull from. Date-based release tags work too, like N'20260407'. |
@Repository | NVARCHAR(128) | N'BrentOzarULTD/SQL-Server-First-Responder-Kit' | owner/repo on GitHub. Point it at your fork if you maintain one. |
@FileName | NVARCHAR(256) | N'Install-All-Scripts.sql' | Path to the file inside the repo. Pass N'sp_Blitz.sql' (etc.) to update one proc at a time. |
@WhatIf | BIT | 0 | 1 fetches and parses the file, prints how many batches would run, and exits without installing anything. |
@Debug | TINYINT | 0 | 1 prints per-batch length and progress. |
@Help | TINYINT | 0 | 1 prints usage info and returns. |
/* Pinned to a release tag, which is what you want for reproducible lab boxes: */
EXEC dbo.sp_BlitzUpdate @Branch = N'20260407';
/* Update a single proc: */
EXEC dbo.sp_BlitzUpdate @FileName = N'sp_Blitz.sql';
/* Preview without installing: */
EXEC dbo.sp_BlitzUpdate @WhatIf = 1, @Debug = 1;
/* From a fork: */
EXEC dbo.sp_BlitzUpdate @Repository = N'yourname/SQL-Server-First-Responder-Kit', @Branch = N'dev';
@Branch instead of leaving it on main when you care about what you're getting.USE master; first, matching the regular FRK install workflow.To check versions of any of the stored procedures, use their output parameters for Version and VersionDate like this:
DECLARE @VersionOutput VARCHAR(30), @VersionDateOutput DATETIME;
EXEC sp_Blitz
@Version = @VersionOutput OUTPUT,
@VersionDate = @VersionDateOutput OUTPUT,
@VersionCheckMode = 1;
SELECT
@VersionOutput AS Version,
@VersionDateOutput AS VersionDate;
644 followers · starred Apr 2019
119 followers · starred Aug 2016
24 followers · starred Jun 2025
144 followers · starred Jul 2019