Skip to content

2026-04-06 Release#3885

Closed
BrentOzar wants to merge 68 commits intomainfrom
dev
Closed

2026-04-06 Release#3885
BrentOzar wants to merge 68 commits intomainfrom
dev

Conversation

@BrentOzar
Copy link
Copy Markdown
Member

No description provided.

Tisit and others added 30 commits December 2, 2025 00:23
…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
Update parameter name from @askai to @ai in README
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>
…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>
BrentOzar and others added 8 commits April 6, 2026 12:21
Fix database ID reference in sp_BlitzCache
Cleaning up files before Claude starts work.
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>
Copilot AI review requested due to automatic review settings April 6, 2026 23:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_kill stored 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 EXISTS patterns.
  • Refactored parts of sp_BlitzCache (plan cache materialization, AI prompt robustness) and enhanced deadlock plan matching in sp_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';
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
@RequestsOlderThanMinutes = 5, @ExecuteKills = 'Y';
@RequestsOlderThanSeconds = 300, @ExecuteKills = 'Y';

Copilot uses AI. Check for mistakes.
Comment on lines +359 to +373
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.
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sp_kill parameter docs don’t match the implementation:

  • @SPIDState is documented as "empty string for both", but the proc validates @SPIDState as 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.

Copilot uses AI. Check for mistakes.
Comment on lines +495 to +503
/* 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
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
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),
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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,

Copilot uses AI. Check for mistakes.
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>
BrentOzar and others added 2 commits April 6, 2026 23:40
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>
BrentOzar and others added 2 commits April 6, 2026 16:47
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
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 774 to 778
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,
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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+).

Copilot uses AI. Check for mistakes.
Comment on lines +1860 to +1864
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)
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
sp_kill.sql Outdated
Comment on lines +653 to +656
/* 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''')
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +142 to +145
$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 }

Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +27
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;
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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+).

Copilot uses AI. Check for mistakes.
- 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>
BrentOzar and others added 5 commits April 7, 2026 04:06
- 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
@BrentOzar BrentOzar closed this Apr 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants