Conversation
…th BEGIN/END Co-authored-by: BrentOzar <245462+BrentOzar@users.noreply.github.com>
…egin-end Wrap all stored procedure bodies with BEGIN/END
…mpts for null plans Co-authored-by: BrentOzar <245462+BrentOzar@users.noreply.github.com>
…2 with null plans Co-authored-by: BrentOzar <245462+BrentOzar@users.noreply.github.com>
…-null-query-plan sp_BlitzCache: Skip AI calls for null query plans, allow @ai=2 prompts without plans
Converts the Brent Ozar Unlimited SQL Server Setup Checklist from Word format to Markdown, covering server provisioning, storage, Windows configuration, SQL Server installation, security, alerting, monitoring, and application deployment. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add SQL Server Setup Checklist documentation
sp_BlitzLock - fix for deadlock group query number issue #3859
Ensure ai_prompt is never NULL when @ai > 0 by removing the QueryPlan IS NOT NULL filter from prompt building, adding explicit messages for missing query text/plan, and overriding with a simple message when both are NULL. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
sp_BlitzCache: fix ai_prompt returning nulls when @ai > 0
New stored procedure that collects all running session data, recommends which queries to kill (with reasons), optionally executes the kills, frees plan cache entries, and logs everything to an output table. Designed for performance emergencies where the alternative is restarting the server. Defaults to read-only mode and requires explicit targeting filters before killing anything. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add version/build gating for sys.dm_exec_query_statistics_xml to avoid assertion errors on problematic SQL Server builds - Escape single quotes in @ParametersUsed string building to handle logins/app names with apostrophes - Remove MAX_GRANT_PERCENT from OPTION hint (not supported on all versions), keep MAXDOP 1 - Fix open_transaction_count to fall back to session-level value for sleeping sessions that have no active request - Fix is_read_only detection to use corrected open_transaction_count - Remove @OmitLogin from safety check since it's an exclusion filter, not an inclusive targeting filter - Add validation that @RequestsOlderThanMinutes must be >= 0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…vars Move license/header comments inside the stored procedure body so they're visible when opening the proc to troubleshoot. Simplify the live query plan DMV check to just EXISTS (if the DMV exists, it's supported - no need to also version-gate). Remove unused variables (@platform, @productversion, @ProductVersionMajor, @ProductVersionMinor, @EngineEdition). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Allow @ExecuteKills = 'Y' with no filters to kill all user sessions except the caller's own SPID. The no-filter guard in the recommendation logic now only applies in display mode (@ExecuteKills = 'N'). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rename #sp_kill_sessions to #hitlist. Replace sequential subtractive filter logic (mark all, then un-mark per filter) with a single UPDATE that combines all filters as AND conditions. This ensures that passing multiple filters like @loginname + @appName only kills sessions matching both, not sessions matching either. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…pport Change @LeadBlockers, @readonly, @SPIDState, @OmitLogin, and @HasOpenTran from string defaults ('N'/'') to NULL. This makes filter detection consistent: NULL = not specified, value = active. EXEC sp_kill @ExecuteKills = 'Y' now cleanly kills all user sessions (except system, rollbacks, and the caller's own SPID) with no filters needed. Display mode with no filters still just shows sessions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Seconds granularity is more useful in production emergencies where you may need to kill all queries running longer than e.g. 10 seconds. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add sp_kill stored procedure (#3864)
…fallback Fixes #3868 — when a plan exceeds 128 levels of XML nesting, populate the QueryPlan column with the text plan wrapped in an XML processing instruction (same technique as sp_QuickieStore) instead of leaving it NULL and telling the user to run dm_exec_text_query_plan manually. Also uses StatementStartOffset/StatementEndOffset for statement-level plan retrieval, and separates truly missing plans into their own UPDATE. Tested on SQL2016, SQL2017, SQL2019, SQL2022, SQL2025. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Not sure if that null can happen, but the coalesce looks like a harmless tweak, so taking it. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Not sure this can happen either, but this also looks harmless, so taking it. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…ed-plans sp_BlitzCache: return oversized plans inline instead of manual query fallback
Raise the minimum SQL Server version from 2008 to 2016, remove dead compatibility branches, modernize syntax, and improve performance. - Raise version floor to SQL Server 2016 in both gate checks - Remove dead code for pre-2016 versions (~80 lines): NULL-substitution ELSE branches, trigger accuracy workaround, deprecated memory clerk columns (single_pages_kb), always-true version guards - Replace 25 IF OBJECT_ID/DROP TABLE patterns with DROP TABLE IF EXISTS - Cache repeated COUNT(*) on filter temp tables into variables - Replace 4 WHILE loops for CSV hex parsing with STRING_SPLIT (~120 lines) - Simplify function stats and row goals version checks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix database ID reference in sp_BlitzCache
Cleaning up files before Claude starts work.
…claude_starts #3881 release prep
20260406 release prep
Replace the old build-only script with a single PowerShell script that automates the full release workflow: bump version numbers, build the Install-*.sql files, install on local SQL Server, run test calls, and draft a PR if everything passes. Test Calls.sql is removed — its content is now embedded in the script. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
#3881 Build better merge/test/release process
There was a problem hiding this comment.
Pull request overview
Release bundle updating First Responder Kit scripts to v8.31 (2026-04-06), modernizing code paths for SQL Server 2016+ features, and adding a new emergency session-killing utility (sp_kill) plus updated documentation and release tooling.
Changes:
- Added new
sp_killstored procedure and wired it into uninstall + README navigation/docs. - Updated multiple procedures to v8.31 and introduced/standardized SQL Server 2016+ gating and
DROP TABLE IF EXISTSpatterns. - Refactored parts of
sp_BlitzCache(plan cache materialization, AI prompt robustness) and enhanced deadlock plan matching insp_BlitzLock.
Reviewed changes
Copilot reviewed 19 out of 21 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| Uninstall.sql | Adds sp_kill to the uninstall list (fixes UNION chaining). |
| sp_kill.sql | New procedure to recommend/execute KILL actions with optional logging and cache clearing. |
| sp_ineachdb.sql | Bumps version/date to 8.31/20260406. |
| sp_DatabaseRestore.sql | Bumps version/date and wraps proc body with BEGIN/END. |
| sp_BlitzWho.sql | Adds session SET options + SQL 2016+ gating; modernizes temp table drops and removes older-version branches. |
| sp_BlitzLock.sql | Adds stmt offsets for deadlock stack frames; adjusts ranking/ordering and tightens plan matching by statement offsets. |
| sp_BlitzIndex.sql | Adds SQL 2016+ gating, modernizes temp table drops, removes older-version branches, bumps version/date, adds BEGIN/END. |
| sp_BlitzFirst.sql | Bumps version/date to 8.31/20260406. |
| sp_BlitzCache.sql | Adds SQL 2016+ gating; materializes plan cache by db; improves AI prompt generation and huge-plan handling; bumps version/date. |
| sp_BlitzBackups.sql | Bumps version/date to 8.31/20260406. |
| sp_BlitzAnalysis.sql | Bumps version/date and wraps proc body with BEGIN/END. |
| sp_Blitz.sql | Adds SQL 2016+ gating; modernizes temp table drops; removes legacy branches; bumps version/date; adds BEGIN/END. |
| README.md | Adds sp_kill section + updates AI param naming references; updates navigation. |
| Documentation/Setup_Checklist_SQL_Server.md | Adds a new SQL Server setup checklist doc. |
| Documentation/Development/Test sp_DatabaseRestore.sql | Removed legacy test script. |
| Documentation/Development/ReleaseProcess.md | Removed legacy release process doc. |
| Documentation/Development/Merge Blitz.ps1 | Removed legacy merge script. |
| Documentation/Development/Build Installation Scripts.ps1 | Adds a new automated release/test/build script. |
| Documentation/Development/_TestBed.sql | Removed legacy testbed script. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
README.md
Outdated
|
|
||
| /* Kill sleeping sessions with open transactions idle for 5+ minutes: */ | ||
| EXEC sp_kill @HasOpenTran = 'Y', @SPIDState = 'S', | ||
| @RequestsOlderThanMinutes = 5, @ExecuteKills = 'Y'; |
There was a problem hiding this comment.
The sp_kill README example uses @RequestsOlderThanMinutes, but the stored procedure parameter is @RequestsOlderThanSeconds. As written, this example will fail with an unknown parameter error unless the proc adds a minutes parameter/alias.
| @RequestsOlderThanMinutes = 5, @ExecuteKills = 'Y'; | |
| @RequestsOlderThanSeconds = 300, @ExecuteKills = 'Y'; |
| Parameters for targeting sessions: | ||
|
|
||
| * @ExecuteKills = 'Y' or 'N' (default 'N') - whether to actually kill, or just show recommendations. When set to 'Y', you must also specify at least one targeting filter to prevent accidentally killing everything. | ||
| * @SPID - target a specific session ID. | ||
| * @LoginName - kill sessions belonging to this login. | ||
| * @AppName - kill sessions from this application (supports LIKE wildcards). | ||
| * @DatabaseName - kill sessions using this database. | ||
| * @HostName - kill sessions from this host. | ||
| * @LeadBlockers = 'Y' - kill only lead blockers (sessions blocking others but not blocked themselves). | ||
| * @ReadOnly = 'Y' - only kill read-only queries (SELECTs with no writes). | ||
| * @SPIDState - 'S' for sleeping sessions only, 'R' for running only, empty string for both (default). | ||
| * @HasOpenTran = 'Y' - only target sessions with open transactions. Combine with @SPIDState = 'S' to catch sleeping sessions with forgotten transactions. | ||
| * @RequestsOlderThanMinutes - only target sessions whose last request started at least this many minutes ago. | ||
| * @OmitLogin - exclude sessions belonging to this login from kill recommendations. | ||
| * @OrderBy - sort order for kill recommendations: duration (default), cpu, reads, writes, tempdb, transactions. Useful when you want to kill the worst offenders one by one. |
There was a problem hiding this comment.
sp_kill parameter docs don’t match the implementation:
@SPIDStateis documented as "empty string for both", but the proc validates@SPIDStateas NULL/S/R ('' will raise an error).- Docs say
@ExecuteKills='Y'requires at least one targeting filter, but the proc currently recommends/kills all sessions when no filters are provided.
Please align the docs with the actual behavior, or add enforcement in the proc to match the documented safety requirement.
| /* 7c: Otherwise, apply all filters as AND conditions in a single pass. | ||
| A session must match ALL specified filters to be recommended for kill. | ||
| With no filters, @ExecuteKills = 'Y' kills everything; | ||
| @ExecuteKills = 'N' just shows sessions without recommending. */ | ||
| ELSE | ||
| BEGIN | ||
| /* When in display mode with no filters, just show sessions without recommending kills */ | ||
| IF @ExecuteKills = 'N' | ||
| AND @LoginName IS NULL |
There was a problem hiding this comment.
With @ExecuteKills = 'Y' and no targeting filters, the code path in section 7c marks every non-rollback user session as KillRecommended = 1, which means the kill loop will attempt to kill everything. This contradicts the README’s stated safety requirement, and is risky for an emergency tool.
Consider adding a guard that refuses to execute kills unless at least one targeting filter (or an explicit "kill all" confirmation parameter) is supplied.
sp_BlitzLock.sql
Outdated
| DENSE_RANK() OVER (ORDER BY dp.event_date), | ||
| qn = | ||
| ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, | ||
| ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date), |
There was a problem hiding this comment.
qn is now computed as ROW_NUMBER() (1-based), but later logic still treats it as if it were 0-based (e.g., parallel deadlocks filter d.qn < 2 and the deadlock_group label has a WHEN d.qn = 0 branch). This changes behavior: for parallel deadlocks it will now exclude the second query row (qn=2) from results.
Either revert to ROW_NUMBER() ... - 1 or update the downstream filters/labeling to be consistently 1-based.
| ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date), | |
| ROW_NUMBER() OVER (PARTITION BY dp.event_date ORDER BY dp.event_date) - 1, |
Addresses Copilot review feedback from PR #3885: - @RequestsOlderThanMinutes -> @RequestsOlderThanSeconds (param name) - @SPIDState default is NULL, not empty string - @ExecuteKills docs now accurately reflect that no filter = kill all Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix sp_kill README docs to match proc behavior
The qn column was changed from ROW_NUMBER() - 1 (0-based) to ROW_NUMBER() (1-based), but downstream code still expects 0-based values. This caused parallel deadlocks to silently drop the second query (d.qn < 2 only keeps 1 row instead of 2) and made the WHEN d.qn = 0 labeling branch dead code. Fixes issue identified by Copilot in PR #3885. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The CASE expression mapped qn=0 to "Query #1" but then used qn directly for all other values, so qn=1 also displayed as "Query #1". Replace the CASE with a simple qn + 1 conversion so labels are always correct (qn=0 → "Query #1", qn=1 → "Query #2", etc). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix sp_BlitzLock parallel deadlock filtering regression
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 21 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| qmg.dop AS degree_of_parallelism , ' | ||
| + | ||
| CASE @EnhanceFlag | ||
| WHEN 1 THEN N'query_stats.last_dop, | ||
| + N'query_stats.last_dop, | ||
| query_stats.min_dop, | ||
| query_stats.max_dop, | ||
| query_stats.last_grant_kb, |
There was a problem hiding this comment.
In ExpertMode, the dynamic SELECT now unconditionally references sys.dm_exec_query_stats columns like last_dop/min_dop/max_dop/last_grant_kb. Those columns aren’t available on all SQL Server 2016 builds (they were previously gated via a build check), so this can raise an "Invalid column name" error on 2016 RTM/SP1. Either restore a column-existence/build check and project NULLs when absent, or tighten the minimum supported build (e.g., 2016 SP2+).
| INSERT INTO #only_sql_handles (sql_handle) | ||
| SELECT CAST('' AS XML).value('xs:hexBinary( substring(sql:column("v.val"), sql:column("t.pos")) )', 'varbinary(max)') | ||
| FROM STRING_SPLIT(@OnlySqlHandles, ',') AS ss | ||
| CROSS APPLY (SELECT LTRIM(RTRIM(ss.value))) AS v(val) | ||
| CROSS APPLY (SELECT CASE WHEN SUBSTRING(v.val, 1, 2) = '0x' THEN 3 ELSE 1 END) AS t(pos) |
There was a problem hiding this comment.
This new parsing logic relies on STRING_SPLIT, which requires database compatibility level 130+. README explicitly says the kit can be installed in any database, so installing/running sp_BlitzCache in a DB with lower compatibility will fail here. Consider either (a) retaining a non-STRING_SPLIT fallback splitter, or (b) explicitly validating compatibility_level >= 130 up front and raising a clear error message.
sp_kill.sql
Outdated
| /* Create table if it doesn't exist */ | ||
| SET @StringToExecute = N'USE ' + @OutputDatabaseName + N'; | ||
| IF EXISTS(SELECT * FROM ' + @OutputDatabaseName + N'.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = ''' + @OutputSchemaName + N''') | ||
| AND NOT EXISTS (SELECT * FROM ' + @OutputDatabaseName + N'.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = ''' + @OutputSchemaName + N''' AND QUOTENAME(TABLE_NAME) = ''' + @OutputTableName + N''') |
There was a problem hiding this comment.
The persistent output-table dynamic SQL uses USE <db> (line 654). USE isn’t supported in Azure SQL Database, yet the proc detects Azure (@AzureSQLDB) and even defaults @OutputDatabaseName to the current DB. Consider skipping the USE statement when @AzureSQLDB = 1 (and avoid 3-part naming), or otherwise block output logging on Azure with a clear message.
| $azureContent = Get-ChildItem -Path $RepoRoot -Filter "sp_Blitz*.sql" | | ||
| Where-Object { $_.Name -ne "sp_Blitz.sql" -and $_.Name -notlike "*BlitzBackups*" -and $_.Name -notlike "*DatabaseRestore*" -and $_.Name -notlike "*BlitzFirst*" } | | ||
| ForEach-Object { Get-Content $_.FullName -Raw } | ||
|
|
There was a problem hiding this comment.
The merged Install-*.sql contents depend on the enumeration order of Get-ChildItem, which isn’t guaranteed to be stable across platforms/filesystems. To make releases reproducible (and avoid ordering-dependent install failures), consider explicitly sorting the file list (e.g., Get-ChildItem ... | Sort-Object Name) before concatenating content for both Install-Azure.sql and Install-All-Scripts.sql.
| BEGIN | ||
| DECLARE @msg VARCHAR(8000); | ||
| SELECT @msg = 'Sorry, sp_BlitzIndex doesn''t work on versions of SQL prior to 2016.' + REPLICATE(CHAR(13), 7933); | ||
| PRINT @msg; | ||
| RETURN; |
There was a problem hiding this comment.
The new version gate blocks SQL Server versions prior to 2016, but the procedure’s help text later in the file still lists older version limitations (e.g., mentions 2005/2000). To avoid confusing users, update the “Known limitations” help output to match the new minimum supported version (2016+).
- sp_BlitzWho: Add SQL Server 2016 SP2+ minimum build check (build 5026) - sp_BlitzCache: Check compat level 130+ up front for STRING_SPLIT support - sp_kill: Remove USE statement for Azure SQL DB compatibility, use 3-part naming - sp_BlitzIndex: Update help text to say "2014 and older" instead of "2005 and 2000" Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- sp_kill: Branch on @AzureSQLDB for output table creation — use 2-part names on Azure SQL DB, 3-part names on on-prem/MI - sp_BlitzWho: Update help text to say "2016 SP2 and newer" to match the version gate Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The post-kill UPDATE at line 853 was unconditionally rebuilding @ObjectFullName as a 3-part name, which would fail on Azure SQL DB. Apply the same @AzureSQLDB branching used in Section 10. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
#3888 Fix 4 compatibility issues from Copilot review
Adding Azure SQL DB tests
No description provided.