From 60cee45c443a166a70712ed759d3341b6c39709b Mon Sep 17 00:00:00 2001 From: rodik Date: Wed, 16 Jul 2025 14:52:31 +0200 Subject: [PATCH 01/27] look for existing output table in sys.schemas --- sp_BlitzLock.sql | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index c0a4589d..bf97f197 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -589,19 +589,17 @@ BEGIN @StringToExecute = N'SELECT @r = o.name FROM ' + @OutputDatabaseName + - N'.sys.objects AS o WHERE o.type_desc = N''USER_TABLE'' AND o.name = ' + + N'.sys.objects AS o inner join ' + + @OutputDatabaseName + + N'.sys.schemas as s on o.schema_id = s.schema_id WHERE o.type_desc = N''USER_TABLE'' AND o.name = ' + QUOTENAME ( @OutputTableName, N'''' ) + - N' AND o.schema_id = SCHEMA_ID(' + - QUOTENAME - ( - @OutputSchemaName, - N'''' - ) + - N');', + N' AND s.name =''' + + @OutputSchemaName + + N''';', @StringToExecuteParams = N'@r sysname OUTPUT'; From 802f35f718b84486e5d011bb8e9f55c8b3afde1c Mon Sep 17 00:00:00 2001 From: rodik Date: Wed, 16 Jul 2025 14:55:05 +0200 Subject: [PATCH 02/27] add explicit schema to synonym --- sp_BlitzLock.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index bf97f197..bd280b17 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -841,12 +841,12 @@ BEGIN ) BEGIN RAISERROR('Found synonym DeadlockFindings, dropping', 0, 1) WITH NOWAIT; - DROP SYNONYM DeadlockFindings; + DROP SYNONYM dbo.DeadlockFindings; END; RAISERROR('Creating synonym DeadlockFindings', 0, 1) WITH NOWAIT; SET @StringToExecute = - N'CREATE SYNONYM DeadlockFindings FOR ' + + N'CREATE SYNONYM dbo.DeadlockFindings FOR ' + @OutputDatabaseName + N'.' + @OutputSchemaName + @@ -868,12 +868,12 @@ BEGIN ) BEGIN RAISERROR('Found synonym DeadLockTbl, dropping', 0, 1) WITH NOWAIT; - DROP SYNONYM DeadLockTbl; + DROP SYNONYM dbo.DeadLockTbl; END; RAISERROR('Creating synonym DeadLockTbl', 0, 1) WITH NOWAIT; SET @StringToExecute = - N'CREATE SYNONYM DeadLockTbl FOR ' + + N'CREATE SYNONYM dbo.DeadLockTbl FOR ' + @OutputDatabaseName + N'.' + @OutputSchemaName + @@ -4103,7 +4103,7 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - DROP SYNONYM DeadLockTbl; + DROP SYNONYM dbo.DeadLockTbl; SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Findings to table %s', 0, 1, @d) WITH NOWAIT; @@ -4133,7 +4133,7 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - DROP SYNONYM DeadlockFindings; /*done with inserting.*/ + DROP SYNONYM dbo.DeadlockFindings; /*done with inserting.*/ END; ELSE /*Output to database is not set output to client app*/ BEGIN From b7e32756da2518b36a6ccc87eaa594a1c0b8c078 Mon Sep 17 00:00:00 2001 From: Ben <60398078+bwiggin10@users.noreply.github.com> Date: Tue, 22 Jul 2025 09:17:40 -0400 Subject: [PATCH 03/27] Set @is_query_store_on Default Value to NULL Changes the default value of parameter @is_query_store_on from 0 to NULL to avoid excluding non-query store databases by default. --- sp_ineachdb.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index a477eb9c..5f81ee42 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -30,7 +30,7 @@ ALTER PROCEDURE [dbo].[sp_ineachdb] @VersionDate datetime = NULL OUTPUT, @VersionCheckMode bit = 0, @is_ag_writeable_copy bit = 0, - @is_query_store_on bit = 0 + @is_query_store_on bit = NULL -- WITH EXECUTE AS OWNER – maybe not a great idea, depending on the security of your system AS BEGIN From 2aa89ef0a18b94cdd8e9c8289a306eda2cf87392 Mon Sep 17 00:00:00 2001 From: Dirk Hondong Date: Tue, 22 Jul 2025 15:47:16 +0200 Subject: [PATCH 04/27] additional wildcard in where condition check 260 / 261 see https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/3673 in rare cases we can get "SQL Server-Agent" as a result back instead of "SQL Server Agent" This will then cause the error "Subquery returned more than 1 value. This is not permitted when the subquery follows = .. " --- sp_Blitz.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index e15695fe..f1e92f4c 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -9867,7 +9867,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 WHERE LOWER(cmdshell_output) = ( SELECT LOWER([service_account]) FROM [sys].[dm_server_services] WHERE [servicename] LIKE 'SQL Server%' - AND [servicename] NOT LIKE 'SQL Server Agent%' + AND [servicename] NOT LIKE 'SQL Server%Agent%' AND [servicename] NOT LIKE 'SQL Server Launchpad%')) BEGIN INSERT INTO #BlitzResults @@ -9913,7 +9913,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 FROM #localadmins WHERE LOWER(cmdshell_output) = ( SELECT LOWER([service_account]) FROM [sys].[dm_server_services] - WHERE [servicename] LIKE 'SQL Server Agent%' + WHERE [servicename] LIKE 'SQL Server%Agent%' AND [servicename] NOT LIKE 'SQL Server Launchpad%')) BEGIN INSERT INTO #BlitzResults @@ -9946,7 +9946,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 FROM #localadmins WHERE LOWER(cmdshell_output) = ( SELECT LOWER([service_account]) FROM [sys].[dm_server_services] - WHERE [servicename] LIKE 'SQL Server Agent%' + WHERE [servicename] LIKE 'SQL Server%Agent%' AND [servicename] NOT LIKE 'SQL Server Launchpad%')) BEGIN INSERT INTO #BlitzResults From f712b794aab846e1075259cd155cdf78eb7ea4e9 Mon Sep 17 00:00:00 2001 From: Dirk Hondong Date: Tue, 22 Jul 2025 16:05:21 +0200 Subject: [PATCH 05/27] check 261 fix temp table for "can't piggyback" as mentioned in comment here: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/3673#issuecomment-3102778402 if the script has to "piggyback" here, it now uses the proper temp table --- sp_Blitz.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index f1e92f4c..4e3dbacc 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -9939,11 +9939,11 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 /*had to use a different table name because SQL Server/SSMS complains when parsing that the table still exists when it gets to the create part*/ IF OBJECT_ID('tempdb..#localadminsag') IS NOT NULL DROP TABLE #localadminsag; CREATE TABLE #localadminsag (cmdshell_output NVARCHAR(1000)); - INSERT INTO #localadmins + INSERT INTO #localadminsag EXEC /**/xp_cmdshell/**/ N'net localgroup administrators' /* added comments around command since some firewalls block this string TL 20210221 */ IF EXISTS (SELECT 1 - FROM #localadmins + FROM #localadminsag WHERE LOWER(cmdshell_output) = ( SELECT LOWER([service_account]) FROM [sys].[dm_server_services] WHERE [servicename] LIKE 'SQL Server%Agent%' From 77157606af458601ac572d6fe4d4b6bedd6068d3 Mon Sep 17 00:00:00 2001 From: Dirk Hondong Date: Wed, 23 Jul 2025 13:23:14 +0200 Subject: [PATCH 06/27] determine German OS for xp_cmdshell call Check 260 and 261 this commit adresses issue 3673 where the "net localgroups administrators" command will not return the proper information since the naming is different in an German OS --- sp_Blitz.sql | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 4e3dbacc..521b16f1 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -9939,8 +9939,17 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 /*had to use a different table name because SQL Server/SSMS complains when parsing that the table still exists when it gets to the create part*/ IF OBJECT_ID('tempdb..#localadminsag') IS NOT NULL DROP TABLE #localadminsag; CREATE TABLE #localadminsag (cmdshell_output NVARCHAR(1000)); - INSERT INTO #localadminsag - EXEC /**/xp_cmdshell/**/ N'net localgroup administrators' /* added comments around command since some firewalls block this string TL 20210221 */ + /* language specific call of xp cmdshell */ + IF (SELECT os_language_version FROM sys.dm_os_windows_info) = 1031 /* os language code for German. Again, this is a very specific fix, see #3673 */ + BEGIN + INSERT INTO #localadminsag + EXEC /**/xp_cmdshell/**/ N'net localgroup Administratoren' /* german */ + END + ELSE + BEGIN + INSERT INTO #localadminsag + EXEC /**/xp_cmdshell/**/ N'net localgroup administrators' /* added comments around command since some firewalls block this string TL 20210221 */ + END IF EXISTS (SELECT 1 FROM #localadminsag From cee92d720c15d4051497dba4fbdbbf601befde3c Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Fri, 25 Jul 2025 11:19:04 -0700 Subject: [PATCH 07/27] #3677 versions updates Fix 2019 CU31/32 swap, add new GDRs. Closes #3677. --- SqlServerVersions.sql | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index 94ae4a0b..cb1bb869 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -45,6 +45,8 @@ VALUES (17, 800, 'CTP 2.1', 'https://info.microsoft.com/ww-landing-sql-server-2025.html', '2025-06-16', '2025-12-31', '2025-12-31', 'SQL Server 2025', 'Preview CTP 2.1'), (17, 700, 'CTP 2.0', 'https://info.microsoft.com/ww-landing-sql-server-2025.html', '2025-05-19', '2025-12-31', '2025-12-31', 'SQL Server 2025', 'Preview CTP 2.0'), /*2022*/ + (16, 4205, 'CU20', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate20', '2025-07-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 20'), + (16, 4200, 'CU19 GDR', 'https://support.microsoft.com/en-us/help/5058721', '2025-07-08', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 19 GDR'), (16, 4195, 'CU19', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate19', '2025-05-19', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 19'), (16, 4185, 'CU18', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate18', '2025-03-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 18'), (16, 4175, 'CU17', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate17', '2025-01-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 17'), @@ -70,8 +72,9 @@ VALUES (16, 1050, 'RTM GDR', 'https://support.microsoft.com/kb/5021522', '2023-02-14', '2028-01-11', '2033-01-11', 'SQL Server 2022 GDR', 'RTM'), (16, 1000, 'RTM', '', '2022-11-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'RTM'), /*2019*/ - (15, 4420, 'CU32', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2019/cumulativeupdate32', '2025-02-27', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 32'), - (15, 4430, 'CU31', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2019/cumulativeupdate31', '2025-02-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 31'), + (15, 4435, 'CU32 GDR', 'https://support.microsoft.com/kb/5058722', '2025-07-08', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 32 GDR'), + (15, 4430, 'CU32', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2019/cumulativeupdate32', '2025-02-27', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 32'), + (15, 4420, 'CU31', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2019/cumulativeupdate31', '2025-02-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 31'), (15, 4415, 'CU30', 'https://support.microsoft.com/kb/5049235', '2024-12-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 30'), (15, 4405, 'CU29', 'https://support.microsoft.com/kb/5046365', '2024-10-31', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 29'), (15, 4395, 'CU28 GDR', 'https://support.microsoft.com/kb/5046060', '2024-10-08', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 28 GDR'), @@ -110,6 +113,7 @@ VALUES (15, 2070, 'GDR', 'https://support.microsoft.com/en-us/help/4517790', '2019-11-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'RTM GDR '), (15, 2000, 'RTM ', '', '2019-11-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'RTM '), /*2017*/ + (14, 3495, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5058714', '2025-07-08', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3485, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5046858', '2024-11-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3480, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5046061', '2024-10-08', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3475, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5042215', '2024-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), @@ -151,6 +155,7 @@ VALUES (14, 3006, 'RTM CU1', 'https://support.microsoft.com/en-us/help/4038634', '2017-10-24', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 1'), (14, 1000, 'RTM ', '', '2017-10-02', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM '), /*2016*/ + (13, 7055, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5058717', '2025-07-08', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'), (13, 7045, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5046062', '2024-10-08', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'), (13, 7040, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5042209', '2024-09-10', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'), (13, 7037, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5040944', '2024-07-09', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'), From 7fdf6025b612fda4cf7dd5c46fa3b5a5482318a7 Mon Sep 17 00:00:00 2001 From: DForck42 Date: Thu, 7 Aug 2025 13:15:33 -0500 Subject: [PATCH 08/27] fix for sp conversion info length --- sp_BlitzCache.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 19e02afe..7d4175fd 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -4072,12 +4072,12 @@ SELECT @@SPID AS SPID, AND ci.comma_paren_charindex > 0 THEN SUBSTRING(ci.expression, ci.paren_charindex, ci.comma_paren_charindex) END AS converted_to, - CASE WHEN ci.at_charindex = 0 + LEFT(CASE WHEN ci.at_charindex = 0 AND ci.convert_implicit_charindex = 0 AND ci.proc_name = 'Statement' THEN SUBSTRING(ci.expression, ci.equal_charindex, 4000) ELSE '**idk_man**' - END AS compile_time_value + END, 258) AS compile_time_value FROM #conversion_info AS ci OPTION (RECOMPILE); From f919198708712fccb7db2407518c06d847e44d07 Mon Sep 17 00:00:00 2001 From: Klaas Date: Tue, 19 Aug 2025 14:56:01 +0200 Subject: [PATCH 09/27] Update sp_Blitz.sql add checks to skip on MI skip IFI, failsafe operator and sql agent alerts on managed instance --- sp_Blitz.sql | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 521b16f1..41eceb78 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -864,12 +864,17 @@ AS INSERT INTO #SkipChecks (CheckID) VALUES (6); /* Security - Jobs Owned By Users per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ INSERT INTO #SkipChecks (CheckID) VALUES (21); /* Informational - Database Encrypted per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ INSERT INTO #SkipChecks (CheckID) VALUES (24); /* File Configuration - System Database on C Drive per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ + INSERT INTO #SkipChecks (CheckID) VALUES (30); /* SQL Agent Alerts cannot be configured on MI */ INSERT INTO #SkipChecks (CheckID) VALUES (50); /* Max Server Memory Set Too High - because they max it out */ INSERT INTO #SkipChecks (CheckID) VALUES (55); /* Security - Database Owner <> sa per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ + INSERT INTO #SkipChecks (CheckID) VALUES (61); /* SQL Agent Alerts cannot be configured on MI */ + INSERT INTO #SkipChecks (CheckID) VALUES (73); /* SQL Agent Failsafe Operator cannot be configured on MI */ INSERT INTO #SkipChecks (CheckID) VALUES (74); /* TraceFlag On - because Azure Managed Instances go wild and crazy with the trace flags */ + INSERT INTO #SkipChecks (CheckID) VALUES (96); /* SQL Agent Alerts cannot be configured on MI */ INSERT INTO #SkipChecks (CheckID) VALUES (97); /* Unusual SQL Server Edition */ INSERT INTO #SkipChecks (CheckID) VALUES (100); /* Remote DAC disabled - but it's working anyway, details here: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1481 */ INSERT INTO #SkipChecks (CheckID) VALUES (186); /* MSDB Backup History Purged Too Frequently */ + INSERT INTO #SkipChecks (CheckID) VALUES (192); /* IFI can not be set for data files and is always used for log files in MI */ INSERT INTO #SkipChecks (CheckID) VALUES (199); /* Default trace, details here: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1481 */ INSERT INTO #SkipChecks (CheckID) VALUES (211); /*Power Plan */ INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'master'); /* Max file size set */ From 6a2a133a80eda566150fe518b56f3aa300cd397f Mon Sep 17 00:00:00 2001 From: James Davis Date: Tue, 19 Aug 2025 11:30:37 -0500 Subject: [PATCH 10/27] Added @UsualOwnerOfJobs to be used in CheckID 6 Jobs Owned By Users to set your default SA user. I did not use @UsualDBOwner you do not have these the same. Amend --- sp_Blitz.sql | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 521b16f1..97bcf064 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -26,6 +26,7 @@ ALTER PROCEDURE [dbo].[sp_Blitz] @SummaryMode TINYINT = 0 , @BringThePain TINYINT = 0 , @UsualDBOwner sysname = NULL , + @UsualOwnerOfJobs sysname = NULL , -- This is to set the owner of Jobs is you have a different account than SA that you use as Default @SkipBlockingChecks TINYINT = 1 , @Debug TINYINT = 0 , @Version VARCHAR(30) = NULL OUTPUT, @@ -1932,7 +1933,11 @@ AS BEGIN IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 6) WITH NOWAIT; + + IF @UsualOwnerOfJobs IS NULL + SET @UsualOwnerOfJobs = SUSER_SNAME(0x01); + INSERT INTO #BlitzResults ( CheckID , Priority , @@ -1951,7 +1956,7 @@ AS + '] - meaning if their login is disabled or not available due to Active Directory problems, the job will stop working.' ) AS Details FROM msdb.dbo.sysjobs j WHERE j.enabled = 1 - AND SUSER_SNAME(j.owner_sid) <> SUSER_SNAME(0x01); + AND SUSER_SNAME(j.owner_sid) <> @UsualOwnerOfJobs; END; /* --TOURSTOP06-- */ From 4ae548e1718de5f25f3103e76c418c6f74f5de2a Mon Sep 17 00:00:00 2001 From: James Davis Date: Wed, 20 Aug 2025 07:31:39 -0500 Subject: [PATCH 11/27] Changed CheckID 183 TempDB Unevenly Sized Data Files to Percent Difference Instead of a fixed 1GB difference I use a over default or passed in percent. I went with 10% as DDefault but do not mind that being changed. --- sp_Blitz.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 97bcf064..0b6403a0 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -27,6 +27,7 @@ ALTER PROCEDURE [dbo].[sp_Blitz] @BringThePain TINYINT = 0 , @UsualDBOwner sysname = NULL , @UsualOwnerOfJobs sysname = NULL , -- This is to set the owner of Jobs is you have a different account than SA that you use as Default + @MaxPercentTembdbFileVariation DECIMAL(3,2) = 0.10, -- set to a decent Default percent, I went with ten as a good start @SkipBlockingChecks TINYINT = 1 , @Debug TINYINT = 0 , @Version VARCHAR(30) = NULL OUTPUT, @@ -3207,11 +3208,10 @@ AS BEGIN - IF ( SELECT COUNT (distinct [size]) + IF (SELECT (MAX((size * 8)) * 1.0)/(MIN((size * 8)) *1.0) - 1.0 FROM tempdb.sys.database_files WHERE type_desc = 'ROWS' - HAVING MAX((size * 8) / (1024. * 1024)) - MIN((size * 8) / (1024. * 1024)) > 1. - ) <> 1 + ) > @MaxPercentTembdbFileVariation BEGIN IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 183) WITH NOWAIT; From 9ad73060fc07c84b8b1a8301eae3a02e1cd6045b Mon Sep 17 00:00:00 2001 From: James Davis Date: Wed, 20 Aug 2025 14:30:45 -0500 Subject: [PATCH 12/27] Removing other change that got declined. --- sp_Blitz.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 0b6403a0..97bcf064 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -27,7 +27,6 @@ ALTER PROCEDURE [dbo].[sp_Blitz] @BringThePain TINYINT = 0 , @UsualDBOwner sysname = NULL , @UsualOwnerOfJobs sysname = NULL , -- This is to set the owner of Jobs is you have a different account than SA that you use as Default - @MaxPercentTembdbFileVariation DECIMAL(3,2) = 0.10, -- set to a decent Default percent, I went with ten as a good start @SkipBlockingChecks TINYINT = 1 , @Debug TINYINT = 0 , @Version VARCHAR(30) = NULL OUTPUT, @@ -3208,10 +3207,11 @@ AS BEGIN - IF (SELECT (MAX((size * 8)) * 1.0)/(MIN((size * 8)) *1.0) - 1.0 + IF ( SELECT COUNT (distinct [size]) FROM tempdb.sys.database_files WHERE type_desc = 'ROWS' - ) > @MaxPercentTembdbFileVariation + HAVING MAX((size * 8) / (1024. * 1024)) - MIN((size * 8) / (1024. * 1024)) > 1. + ) <> 1 BEGIN IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 183) WITH NOWAIT; From 47b5482e162c05886ebf385ca552eb2a6d3132fe Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Wed, 27 Aug 2025 17:41:04 +0000 Subject: [PATCH 13/27] #3690 sp_Blitz memory pressure Add warning about low memory recently. Closes #3690. --- Documentation/sp_Blitz_Checks_by_Priority.md | 5 +-- sp_Blitz.sql | 32 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/Documentation/sp_Blitz_Checks_by_Priority.md b/Documentation/sp_Blitz_Checks_by_Priority.md index c81a9770..d59eeb85 100644 --- a/Documentation/sp_Blitz_Checks_by_Priority.md +++ b/Documentation/sp_Blitz_Checks_by_Priority.md @@ -6,8 +6,8 @@ Before adding a new check, make sure to add a Github issue for it first, and hav If you want to change anything about a check - the priority, finding, URL, or ID - open a Github issue first. The relevant scripts have to be updated too. -CURRENT HIGH CHECKID: 269. -If you want to add a new one, start at 270. +CURRENT HIGH CHECKID: 270. +If you want to add a new one, start at 271. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|-----------------------------|---------------------------------------------------------|------------------------------------------------------------------------|----------| @@ -26,6 +26,7 @@ If you want to add a new one, start at 270. | 1 | Corruption | Database Corruption Detected | https://www.BrentOzar.com/go/repair | 90 | | 1 | Performance | Memory Dangerously Low | https://www.BrentOzar.com/go/max | 51 | | 1 | Performance | Memory Dangerously Low in NUMA Nodes | https://www.BrentOzar.com/go/max | 159 | +| 1 | Performance | Memory Dangerously Low Recently | https://www.BrentOzar.com/go/memhistory | 270 | | 1 | Reliability | Evaluation Edition | https://www.BrentOzar.com/go/workgroup | 229 | | 1 | Reliability | Last good DBCC CHECKDB over 2 weeks old | https://www.BrentOzar.com/go/checkdb | 68 | | 1 | Security | Dangerous Service Account | https://vladdba.com/SQLServerSvcAccount | 258 | diff --git a/sp_Blitz.sql b/sp_Blitz.sql index e48ee2b7..48ab2efe 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -3936,6 +3936,38 @@ AS AND SUM([wait_time_ms]) > 60000; END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 270 ) + AND EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_os_memory_health_history') + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 270) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 270 AS CheckID , + 1 AS Priority , + 'Performance' AS FindingGroup , + 'Memory Dangerous Low Recently' AS Finding , + 'https://www.brentozar.com/go/memhist' AS URL , + CAST(SUM(1) AS NVARCHAR(10)) + N' instances of ' + CAST(severity_level_desc AS NVARCHAR(100)) + + N' severity level memory issues reported in the last 4 hours in sys.dm_os_memory_health_history.' + FROM sys.dm_os_memory_health_history + WHERE severity_level > 1 + GROUP BY severity_level, severity_level_desc; + END; + + + + + IF NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 121 ) From dd66b83311ad80fc63536d8fd8570c05cb375a53 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Wed, 27 Aug 2025 17:59:32 +0000 Subject: [PATCH 14/27] #3692 sp_BlitzFirst memory pressure Adds new warning for sys.dm_os_memory_health_history. Closes #3692. --- .../sp_BlitzFirst_Checks_by_Priority.md | 5 +++-- sp_BlitzFirst.sql | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Documentation/sp_BlitzFirst_Checks_by_Priority.md b/Documentation/sp_BlitzFirst_Checks_by_Priority.md index b81ea550..36d2bbe7 100644 --- a/Documentation/sp_BlitzFirst_Checks_by_Priority.md +++ b/Documentation/sp_BlitzFirst_Checks_by_Priority.md @@ -6,8 +6,8 @@ Before adding a new check, make sure to add a Github issue for it first, and hav If you want to change anything about a check - the priority, finding, URL, or ID - open a Github issue first. The relevant scripts have to be updated too. -CURRENT HIGH CHECKID: 51 -If you want to add a new check, start at 52. +CURRENT HIGH CHECKID: 52 +If you want to add a new check, start at 53. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|---------------------------------|---------------------------------------|-------------------------------------------------|----------| @@ -23,6 +23,7 @@ If you want to add a new check, start at 52. | 1 | SQL Server Internal Maintenance | Data File Growing | https://www.brentozar.com/go/instant | 4 | | 1 | SQL Server Internal Maintenance | Log File Growing | https://www.brentozar.com/go/logsize | 13 | | 1 | SQL Server Internal Maintenance | Log File Shrinking | https://www.brentozar.com/go/logsize | 14 | +| 10 | Server Performance | Memory Dangerously Low Recently | https://www.brentozar.com/go/memhist | 52 | | 10 | Server Performance | Poison Wait Detected | https://www.brentozar.com/go/poison | 30 | | 10 | Server Performance | Target Memory Lower Than Max | https://www.brentozar.com/go/target | 35 | | 10 | Azure Performance | Database is Maxed Out | https://www.brentozar.com/go/maxedout | 41 | diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 28a20f9e..5e08c128 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2617,6 +2617,27 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END END + + + /* Server Performance - Memory Dangerously Low Recently - CheckID 52 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 52',10,1) WITH NOWAIT; + END + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_os_memory_health_history') + BEGIN + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT TOP 1 52 AS CheckID, + 10 AS Priority, + 'Server Performance' AS FindingGroup, + 'Memory Dangerously Low Recently' AS Finding, + 'https://www.brentozar.com/go/memhist' AS URL, + N'As recently as ' + CONVERT(NVARCHAR(19), snapshot_time, 120) + N', memory health issues are being reported in sys.dm_os_memory_health, indicating extreme memory pressure.' AS Details + FROM sys.dm_os_memory_health_history + WHERE severity_level > 1; + END + + RAISERROR('Finished running investigatory queries',10,1) WITH NOWAIT; From 6f0e2043f56bf1b61d8fa761f2e90f94c35f9f5d Mon Sep 17 00:00:00 2001 From: ReeceGoding <67124261+ReeceGoding@users.noreply.github.com> Date: Sun, 3 Aug 2025 14:46:43 +0100 Subject: [PATCH 15/27] sp_BlitzIndex: Added warning for persisted sample rates. --- .../sp_BlitzIndex_Checks_by_Priority.md | 5 ++- sp_BlitzIndex.sql | 41 +++++++++++++++---- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/Documentation/sp_BlitzIndex_Checks_by_Priority.md b/Documentation/sp_BlitzIndex_Checks_by_Priority.md index 619438d0..1b64b5d3 100644 --- a/Documentation/sp_BlitzIndex_Checks_by_Priority.md +++ b/Documentation/sp_BlitzIndex_Checks_by_Priority.md @@ -6,8 +6,8 @@ Before adding a new check, make sure to add a Github issue for it first, and hav If you want to change anything about a check - the priority, finding, URL, or ID - open a Github issue first. The relevant scripts have to be updated too. -CURRENT HIGH CHECKID: 124 -If you want to add a new check, start at 125. +CURRENT HIGH CHECKID: 125 +If you want to add a new check, start at 126. | Priority | FindingsGroup | Finding | URL | CheckID | | -------- | ----------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------- | ------- | @@ -25,6 +25,7 @@ If you want to add a new check, start at 125. | 80 | Abnormal Design Pattern | Filter Columns Not In Index Definition | https://www.brentozar.com/go/IndexFeatures | 34 | | 80 | Abnormal Design Pattern | History Table With NonClustered Index | https://sqlserverfast.com/blog/hugo/2023/09/an-update-on-merge/ | 124 | | 90 | Statistics Warnings | Low Sampling Rates | https://www.brentozar.com/go/stats | 91 | +| 90 | Statistics Warnings | Persisted Sampling Rates | https://www.youtube.com/watch?v=V5illj_KOJg&t=758s | 125 | | 90 | Statistics Warnings | Statistics Not Updated Recently | https://www.brentozar.com/go/stats | 90 | | 90 | Statistics Warnings | Statistics with NO RECOMPUTE | https://www.brentozar.com/go/stats | 92 | | 100 | Over-Indexing | NC index with High Writes:Reads | https://www.brentozar.com/go/IndexHoarder | 48 | diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 629cf39c..c37e755a 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -738,7 +738,8 @@ IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL table_modify_date DATETIME NULL, no_recompute BIT NULL, has_filter BIT NULL, - filter_definition NVARCHAR(MAX) NULL + filter_definition NVARCHAR(MAX) NULL, + has_persisted_sample BIT NULL ); CREATE TABLE #ComputedColumns @@ -2279,7 +2280,7 @@ OPTION (RECOMPILE);'; INSERT #Statistics ( database_id, database_name, table_name, schema_name, index_name, column_names, statistics_name, last_statistics_update, days_since_last_stats_update, rows, rows_sampled, percent_sampled, histogram_steps, modification_counter, percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date, - no_recompute, has_filter, filter_definition) + no_recompute, has_filter, filter_definition, has_persisted_sample) SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], @i_DatabaseName AS database_name, obj.name AS table_name, @@ -2306,7 +2307,12 @@ OPTION (RECOMPILE);'; CONVERT(DATETIME, obj.modify_date) AS table_modify_date, s.no_recompute, s.has_filter, - s.filter_definition + s.filter_definition, + ' + + CASE WHEN (PARSENAME(@SQLServerProductVersion, 4) >= 15) + THEN N's.has_persisted_sample' + ELSE N'NULL AS has_persisted_sample' END + + N' FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj ON s.object_id = obj.object_id @@ -2354,7 +2360,7 @@ OPTION (RECOMPILE);'; INSERT #Statistics(database_id, database_name, table_name, schema_name, index_name, column_names, statistics_name, last_statistics_update, days_since_last_stats_update, rows, modification_counter, percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date, - no_recompute, has_filter, filter_definition) + no_recompute, has_filter, filter_definition, has_persisted_sample) SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], @i_DatabaseName AS database_name, obj.name AS table_name, @@ -2379,9 +2385,11 @@ OPTION (RECOMPILE);'; ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N's.has_filter, - s.filter_definition' + s.filter_definition,' ELSE N'NULL AS has_filter, - NULL AS filter_definition' END + NULL AS filter_definition,' END + /* If we are on this branch, then we cannot have the has_persisted_sample column (it is a 2019+ column). */ + + N'NULL AS has_persisted_sample' + N' FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.sysindexes si @@ -4454,7 +4462,7 @@ BEGIN END; ---------------------------------------- - --Statistics Info: Check_id 90-99 + --Statistics Info: Check_id 90-99, as well as 125 ---------------------------------------- RAISERROR(N'check_id 90: Outdated statistics', 0,1) WITH NOWAIT; @@ -4503,6 +4511,24 @@ BEGIN OR (s.rows > 1000000 AND s.percent_sampled < 1) OPTION ( RECOMPILE ); + RAISERROR(N'check_id 125: Statistics with a persisted sample rate', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 125 AS check_id, + 90 AS Priority, + 'Statistics Warnings' AS findings_group, + 'Persisted Sampling Rates', + s.database_name, + 'https://www.youtube.com/watch?v=V5illj_KOJg&t=758s' AS URL, + 'The statistics sample rate/amount has been persisted here. ' + CONVERT(NVARCHAR(100), s.percent_sampled) + '% of the rows were sampled during the last statistics update. This may indicate that somebody is doing statistics rocket surgery. Perhaps you would be better off updating statistics more frequently?' AS details, + QUOTENAME(database_name) + '.' + QUOTENAME(s.schema_name) + '.' + QUOTENAME(s.table_name) + '.' + QUOTENAME(s.index_name) + '.' + QUOTENAME(s.statistics_name) + '.' + QUOTENAME(s.column_names) AS index_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #Statistics AS s + WHERE s.has_persisted_sample = 1 + OPTION ( RECOMPILE ); + RAISERROR(N'check_id 92: Statistics with NO RECOMPUTE', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) @@ -4521,7 +4547,6 @@ BEGIN WHERE s.no_recompute = 1 OPTION ( RECOMPILE ); - RAISERROR(N'check_id 94: Check Constraints That Reference Functions', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) From c689a58110c191b46f2e64d8a8fe42b560c00884 Mon Sep 17 00:00:00 2001 From: ReeceGoding <67124261+ReeceGoding@users.noreply.github.com> Date: Wed, 27 Aug 2025 21:45:44 +0100 Subject: [PATCH 16/27] Added @UsualStatisticsSamplingPercent parameter and comparison to match. Also replaced version check with a column-existence check. Both of these were as Brent suggested. This introduces the complexity of float comparison, since we now care about the float persisted_sample_percent column and have to compare with it. Also added a new check to distinguish the persisted sample rate being surprising and it being unsurprising. --- .../sp_BlitzIndex_Checks_by_Priority.md | 7 +- README.md | 1 + sp_BlitzIndex.sql | 72 +++++++++++++++---- 3 files changed, 64 insertions(+), 16 deletions(-) diff --git a/Documentation/sp_BlitzIndex_Checks_by_Priority.md b/Documentation/sp_BlitzIndex_Checks_by_Priority.md index 1b64b5d3..4da92c72 100644 --- a/Documentation/sp_BlitzIndex_Checks_by_Priority.md +++ b/Documentation/sp_BlitzIndex_Checks_by_Priority.md @@ -6,8 +6,8 @@ Before adding a new check, make sure to add a Github issue for it first, and hav If you want to change anything about a check - the priority, finding, URL, or ID - open a Github issue first. The relevant scripts have to be updated too. -CURRENT HIGH CHECKID: 125 -If you want to add a new check, start at 126. +CURRENT HIGH CHECKID: 126 +If you want to add a new check, start at 127. | Priority | FindingsGroup | Finding | URL | CheckID | | -------- | ----------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------- | ------- | @@ -25,7 +25,7 @@ If you want to add a new check, start at 126. | 80 | Abnormal Design Pattern | Filter Columns Not In Index Definition | https://www.brentozar.com/go/IndexFeatures | 34 | | 80 | Abnormal Design Pattern | History Table With NonClustered Index | https://sqlserverfast.com/blog/hugo/2023/09/an-update-on-merge/ | 124 | | 90 | Statistics Warnings | Low Sampling Rates | https://www.brentozar.com/go/stats | 91 | -| 90 | Statistics Warnings | Persisted Sampling Rates | https://www.youtube.com/watch?v=V5illj_KOJg&t=758s | 125 | +| 90 | Statistics Warnings | Persisted Sampling Rates (Unexpected) | `https://www.youtube.com/watch?v=V5illj_KOJg&t=758s` | 125 | | 90 | Statistics Warnings | Statistics Not Updated Recently | https://www.brentozar.com/go/stats | 90 | | 90 | Statistics Warnings | Statistics with NO RECOMPUTE | https://www.brentozar.com/go/stats | 92 | | 100 | Over-Indexing | NC index with High Writes:Reads | https://www.brentozar.com/go/IndexHoarder | 48 | @@ -62,6 +62,7 @@ If you want to add a new check, start at 126. | 200 | Abnormal Design Pattern | Temporal Tables | https://www.brentozar.com/go/AbnormalPsychology | 110 | | 200 | Repeated Calculations | Computed Columns Not Persisted | https://www.brentozar.com/go/serialudf | 100 | | 200 | Statistics Warnings | Statistics With Filters | https://www.brentozar.com/go/stats | 93 | +| 200 | Statistics Warnings | Persisted Sampling Rates (Expected) | `https://www.youtube.com/watch?v=V5illj_KOJg&t=758s` | 126 | | 200 | Over-Indexing | High Ratio of Nulls | https://www.brentozar.com/go/IndexHoarder | 25 | | 200 | Over-Indexing | High Ratio of Strings | https://www.brentozar.com/go/IndexHoarder | 27 | | 200 | Over-Indexing | Wide Tables: 35+ cols or > 2000 non-LOB bytes | https://www.brentozar.com/go/IndexHoarder | 26 | diff --git a/README.md b/README.md index bfcf2470..9490300e 100644 --- a/README.md +++ b/README.md @@ -288,6 +288,7 @@ In addition to the [parameters common to many of the stored procedures](#paramet * @SkipPartitions = 1 - add this if you want to analyze large partitioned tables. We skip these by default for performance reasons. * @SkipStatistics = 0 - right now, by default, we skip statistics analysis because we've had some performance issues on this. +* @UsualStatisticsSamplingPercent = 100 (default) - By default, @SkipStatistics = 0 with either @Mode = 0 or @Mode = 4 does not inform you of persisted statistics sample rates if that rate is 100. Use a different float if you usually persist a different sample percentage and do not want to be warned about it. Use NULL if you want to hear about every persisted sample rate. * @Filter = 0 (default) - 1=No low-usage warnings for objects with 0 reads. 2=Only warn for objects >= 500MB * @OutputDatabaseName, @OutputSchemaName, @OutputTableName - these only work for @Mode = 2, index usage detail. diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index c37e755a..4b63b7f8 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -23,6 +23,7 @@ ALTER PROCEDURE dbo.sp_BlitzIndex /*Note:@Filter doesn't do anything unless @Mode=0*/ @SkipPartitions BIT = 0, @SkipStatistics BIT = 1, + @UsualStatisticsSamplingPercent FLOAT = 100, /* FLOAT to match sys.dm_db_stats_properties. More detail later. 100 by default because Brent suggests that if people are persisting statistics at all, they are probably doing 100 in lots of places and not filtering that out would produce noise. */ @GetAllDatabases BIT = 0, @ShowColumnstoreOnly BIT = 0, /* Will show only the Row Group and Segment details for a table with a columnstore index. */ @BringThePain BIT = 0, @@ -739,7 +740,7 @@ IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL no_recompute BIT NULL, has_filter BIT NULL, filter_definition NVARCHAR(MAX) NULL, - has_persisted_sample BIT NULL + persisted_sample_percent FLOAT NULL ); CREATE TABLE #ComputedColumns @@ -2280,7 +2281,7 @@ OPTION (RECOMPILE);'; INSERT #Statistics ( database_id, database_name, table_name, schema_name, index_name, column_names, statistics_name, last_statistics_update, days_since_last_stats_update, rows, rows_sampled, percent_sampled, histogram_steps, modification_counter, percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date, - no_recompute, has_filter, filter_definition, has_persisted_sample) + no_recompute, has_filter, filter_definition, persisted_sample_percent) SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], @i_DatabaseName AS database_name, obj.name AS table_name, @@ -2309,9 +2310,15 @@ OPTION (RECOMPILE);'; s.has_filter, s.filter_definition, ' - + CASE WHEN (PARSENAME(@SQLServerProductVersion, 4) >= 15) - THEN N's.has_persisted_sample' - ELSE N'NULL AS has_persisted_sample' END + + CASE WHEN EXISTS + ( + /* We cannot trust checking version numbers, like we did above, because Azure disagrees. */ + SELECT 1 + FROM sys.all_columns AS all_cols + WHERE all_cols.[object_id] = OBJECT_ID(N'sys.dm_db_stats_properties', N'IF') AND all_cols.[name] = N'persisted_sample_percent' + ) + THEN N'ddsp.persisted_sample_percent' + ELSE N'NULL AS persisted_sample_percent' END + N' FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj @@ -2360,7 +2367,7 @@ OPTION (RECOMPILE);'; INSERT #Statistics(database_id, database_name, table_name, schema_name, index_name, column_names, statistics_name, last_statistics_update, days_since_last_stats_update, rows, modification_counter, percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date, - no_recompute, has_filter, filter_definition, has_persisted_sample) + no_recompute, has_filter, filter_definition, persisted_sample_percent) SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], @i_DatabaseName AS database_name, obj.name AS table_name, @@ -2388,8 +2395,8 @@ OPTION (RECOMPILE);'; s.filter_definition,' ELSE N'NULL AS has_filter, NULL AS filter_definition,' END - /* If we are on this branch, then we cannot have the has_persisted_sample column (it is a 2019+ column). */ - + N'NULL AS has_persisted_sample' + /* Certainly NULL. This branch does not even join on the table that this column comes from. */ + + N'NULL AS persisted_sample_percent' + N' FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.sysindexes si @@ -4462,7 +4469,7 @@ BEGIN END; ---------------------------------------- - --Statistics Info: Check_id 90-99, as well as 125 + --Statistics Info: Check_id 90-99, as well as 125-126 ---------------------------------------- RAISERROR(N'check_id 90: Outdated statistics', 0,1) WITH NOWAIT; @@ -4511,22 +4518,61 @@ BEGIN OR (s.rows > 1000000 AND s.percent_sampled < 1) OPTION ( RECOMPILE ); - RAISERROR(N'check_id 125: Statistics with a persisted sample rate', 0,1) WITH NOWAIT; + RAISERROR(N'check_id 125: Persisted Sampling Rates (Unexpected)', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) SELECT 125 AS check_id, 90 AS Priority, 'Statistics Warnings' AS findings_group, - 'Persisted Sampling Rates', + 'Persisted Sampling Rates (Unexpected)', s.database_name, 'https://www.youtube.com/watch?v=V5illj_KOJg&t=758s' AS URL, - 'The statistics sample rate/amount has been persisted here. ' + CONVERT(NVARCHAR(100), s.percent_sampled) + '% of the rows were sampled during the last statistics update. This may indicate that somebody is doing statistics rocket surgery. Perhaps you would be better off updating statistics more frequently?' AS details, + 'The persisted statistics sample rate is ' + CONVERT(NVARCHAR(100), s.persisted_sample_percent) + '%' + + CASE WHEN @UsualStatisticsSamplingPercent IS NOT NULL + THEN (N' rather than your expected @UsualStatisticsSamplingPercent value of ' + CONVERT(NVARCHAR(100), @UsualStatisticsSamplingPercent) + '%') + ELSE '' + END + + N'. This may indicate that somebody is doing statistics rocket surgery. If not, consider updating statistics more frequently.' AS details, QUOTENAME(database_name) + '.' + QUOTENAME(s.schema_name) + '.' + QUOTENAME(s.table_name) + '.' + QUOTENAME(s.index_name) + '.' + QUOTENAME(s.statistics_name) + '.' + QUOTENAME(s.column_names) AS index_definition, 'N/A' AS secret_columns, 'N/A' AS index_usage_summary, 'N/A' AS index_size_summary FROM #Statistics AS s - WHERE s.has_persisted_sample = 1 + /* + We have to do float comparison here, so it is time to explain why @UsualStatisticsSamplingPercent is a float. + The foremost reason is that it is a float because we are comparing it to the persisted_sample_percent column in sys.dm_db_stats_properties and that column is a float. + You may correctly object that CREATE STATISTICS with a decimal as your WITH SAMPLE [...] PERCENT is a syntax error and conclude that integers are enough. + However, `WITH SAMPLE [...] ROWS` is allowed with PERSIST_SAMPLE_PERCENT = ON and you can use that to persist a non-integer sample rate. + So, yes, we really have to use floats. + */ + WHERE + /* persisted_sample_percent is either zero or NULL when the statistic is not persisted. */ + s.persisted_sample_percent > 0.0001 + AND + ( + ABS(@UsualStatisticsSamplingPercent - s.persisted_sample_percent) > 0.1 + OR @UsualStatisticsSamplingPercent IS NULL + ) + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 126: Persisted Sampling Rates (Expected)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 126 AS check_id, + 200 AS Priority, + 'Statistics Warnings' AS findings_group, + 'Persisted Sampling Rates (Expected)', + s.database_name, + 'https://www.youtube.com/watch?v=V5illj_KOJg&t=758s' AS URL, + CONVERT(NVARCHAR(100), COUNT(*)) + ' statistic(s) with a persisted sample rate matching your desired persisted sample rate, ' + CONVERT(NVARCHAR(100), @UsualStatisticsSamplingPercent) + N'%. Set @UsualStatisticsSamplingPercent to NULL if you want to see all of them in this result set. Its default value is 100.' AS details, + s.database_name + N' (Entire database)' AS index_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #Statistics AS s + WHERE ABS(@UsualStatisticsSamplingPercent - s.persisted_sample_percent) <= 0.1 + AND @UsualStatisticsSamplingPercent IS NOT NULL + GROUP BY s.database_name OPTION ( RECOMPILE ); RAISERROR(N'check_id 92: Statistics with NO RECOMPUTE', 0,1) WITH NOWAIT; From 320e778c2218c56082b1834b068c3f49aa78c5a7 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 28 Aug 2025 20:40:07 +0000 Subject: [PATCH 17/27] #3695 sp_BlitzFirst clarify restore name When restoring a new database. Closes #3695. --- sp_BlitzFirst.sql | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 5e08c128..bfcb66bd 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -1638,7 +1638,11 @@ BEGIN 'Maintenance Tasks Running' AS FindingGroup, 'Restore Running' AS Finding, 'https://www.brentozar.com/askbrent/backups/' AS URL, - 'Restore of ' + COALESCE(DB_NAME(db.resource_database_id), 'Unknown Database') + ' database (' + COALESCE((SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id), 'Unknown') + 'GB) is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' AS Details, + 'Restore of ' + COALESCE(DB_NAME(db.resource_database_id), + (SELECT db1.name FROM sys.databases db1 + LEFT OUTER JOIN sys.databases db2 ON db1.name <> db2.name AND db1.state_desc = db2.state_desc + WHERE db1.state_desc = 'RESTORING' AND db2.name IS NULL), + 'Unknown Database') + ' database (' + COALESCE((SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id), 'Unknown ') + 'GB) is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' AS Details, 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, pl.query_plan AS QueryPlan, r.start_time AS StartTime, From f81d59901d0348e6b0a95925ef8ddc74ffc611b2 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 28 Aug 2025 20:52:59 +0000 Subject: [PATCH 18/27] #3694 sp_BlitzWho live plans On by default in 2022 & newer and Azure. --- sp_BlitzWho.sql | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 5c49699e..0267ff3c 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -22,7 +22,7 @@ ALTER PROCEDURE dbo.sp_BlitzWho @CheckDateOverride DATETIMEOFFSET = NULL, @ShowActualParameters BIT = 0, @GetOuterCommand BIT = 0, - @GetLiveQueryPlan BIT = 0, + @GetLiveQueryPlan BIT = NULL, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0, @@ -85,7 +85,8 @@ RETURN; END; /* @Help = 1 */ /* Get the major and minor build numbers */ -DECLARE @ProductVersion NVARCHAR(128) +DECLARE @ProductVersion NVARCHAR(128) = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) + ,@EngineEdition INT = CAST(SERVERPROPERTY('EngineEdition') AS INT) ,@ProductVersionMajor DECIMAL(10,2) ,@ProductVersionMinor DECIMAL(10,2) ,@Platform NVARCHAR(8) /* Azure or NonAzure are acceptable */ = (SELECT CASE WHEN @@VERSION LIKE '%Azure%' THEN N'Azure' ELSE N'NonAzure' END AS [Platform]) @@ -122,7 +123,6 @@ DECLARE @ProductVersion NVARCHAR(128) /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); -SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1,CHARINDEX('.', @ProductVersion) + 1 ), @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2) @@ -133,6 +133,14 @@ SELECT @OutputTableName = QUOTENAME(@OutputTableName), @LineFeed = CHAR(13) + CHAR(10); +IF @GetLiveQueryPlan IS NULL + BEGIN + IF @ProductVersionMajor >= 16 OR @EngineEdition NOT IN (1, 2, 3, 4) + SET @GetLiveQueryPlan = 1; + ELSE + SET @GetLiveQueryPlan = 0; + END + IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL AND EXISTS ( SELECT * FROM sys.databases @@ -920,7 +928,7 @@ IF @ProductVersionMajor >= 11 END+N' derp.query_plan , CAST(COALESCE(qs_live.Query_Plan, ' + CASE WHEN @GetLiveQueryPlan=1 - THEN '''''' + THEN '''''' ELSE '''''' END +') AS XML From 40ef60c3fe813cf66accb0ea4575bfb5222478ef Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 28 Aug 2025 21:50:24 +0000 Subject: [PATCH 19/27] #3680 sp_BlitzIndex stats sampling Continued work on #3680. Moves 200-priority down to Mode 4, throws error if they pass in an invalid stats sampling rate. --- sp_BlitzIndex.sql | 48 +++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 4b63b7f8..feb81a43 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -175,6 +175,12 @@ BEGIN RETURN; END; +IF(@UsualStatisticsSamplingPercent <= 0 OR @UsualStatisticsSamplingPercent > 100) +BEGIN + RAISERROR('Invalid value for parameter @UsualStatisticsSamplingPercent. Expected: 1 to 100',12,1); + RETURN; +END; + IF(@OutputType = 'TABLE' AND NOT (@OutputTableName IS NULL AND @OutputSchemaName IS NULL AND @OutputDatabaseName IS NULL AND @OutputServerName IS NULL)) BEGIN RAISERROR(N'One or more output parameters specified in combination with TABLE output, changing to NONE output mode', 0,1) WITH NOWAIT; @@ -4555,26 +4561,6 @@ BEGIN ) OPTION ( RECOMPILE ); - RAISERROR(N'check_id 126: Persisted Sampling Rates (Expected)', 0,1) WITH NOWAIT; - INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, - secret_columns, index_usage_summary, index_size_summary ) - SELECT 126 AS check_id, - 200 AS Priority, - 'Statistics Warnings' AS findings_group, - 'Persisted Sampling Rates (Expected)', - s.database_name, - 'https://www.youtube.com/watch?v=V5illj_KOJg&t=758s' AS URL, - CONVERT(NVARCHAR(100), COUNT(*)) + ' statistic(s) with a persisted sample rate matching your desired persisted sample rate, ' + CONVERT(NVARCHAR(100), @UsualStatisticsSamplingPercent) + N'%. Set @UsualStatisticsSamplingPercent to NULL if you want to see all of them in this result set. Its default value is 100.' AS details, - s.database_name + N' (Entire database)' AS index_definition, - 'N/A' AS secret_columns, - 'N/A' AS index_usage_summary, - 'N/A' AS index_size_summary - FROM #Statistics AS s - WHERE ABS(@UsualStatisticsSamplingPercent - s.persisted_sample_percent) <= 0.1 - AND @UsualStatisticsSamplingPercent IS NOT NULL - GROUP BY s.database_name - OPTION ( RECOMPILE ); - RAISERROR(N'check_id 92: Statistics with NO RECOMPUTE', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) @@ -5666,6 +5652,28 @@ BEGIN + RAISERROR(N'check_id 126: Persisted Sampling Rates (Expected)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 126 AS check_id, + 200 AS Priority, + 'Statistics Warnings' AS findings_group, + 'Persisted Sampling Rates (Expected)', + s.database_name, + 'https://www.youtube.com/watch?v=V5illj_KOJg&t=758s' AS URL, + CONVERT(NVARCHAR(100), COUNT(*)) + ' statistic(s) with a persisted sample rate matching your desired persisted sample rate, ' + CONVERT(NVARCHAR(100), @UsualStatisticsSamplingPercent) + N'%. Set @UsualStatisticsSamplingPercent to NULL if you want to see all of them in this result set. Its default value is 100.' AS details, + s.database_name + N' (Entire database)' AS index_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #Statistics AS s + WHERE ABS(@UsualStatisticsSamplingPercent - s.persisted_sample_percent) <= 0.1 + AND @UsualStatisticsSamplingPercent IS NOT NULL + GROUP BY s.database_name + OPTION ( RECOMPILE ); + + + END /* IF @Mode = 4 */ From 81ca079677d9b711d923105ea508b577f79497ff Mon Sep 17 00:00:00 2001 From: ReeceGoding <67124261+ReeceGoding@users.noreply.github.com> Date: Sun, 31 Aug 2025 16:42:01 +0100 Subject: [PATCH 20/27] Edited comments to reflect check id 126 being moved. --- sp_BlitzIndex.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index feb81a43..56d83b58 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -4475,7 +4475,7 @@ BEGIN END; ---------------------------------------- - --Statistics Info: Check_id 90-99, as well as 125-126 + --Statistics Info: Check_id 90-99, as well as 125 ---------------------------------------- RAISERROR(N'check_id 90: Outdated statistics', 0,1) WITH NOWAIT; @@ -5651,7 +5651,7 @@ BEGIN OPTION ( RECOMPILE ); - + /* See check_id 125. */ RAISERROR(N'check_id 126: Persisted Sampling Rates (Expected)', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) From 20c22572c226769f56970b3c90fd3e0f4751cc0b Mon Sep 17 00:00:00 2001 From: ReeceGoding <67124261+ReeceGoding@users.noreply.github.com> Date: Sun, 31 Aug 2025 16:46:20 +0100 Subject: [PATCH 21/27] sp_BlitzIndex: Added check for partitioned tables without incremental statistics. Closes #3699. --- .../sp_BlitzIndex_Checks_by_Priority.md | 135 +++++++++--------- sp_BlitzIndex.sql | 87 +++++++++-- 2 files changed, 143 insertions(+), 79 deletions(-) diff --git a/Documentation/sp_BlitzIndex_Checks_by_Priority.md b/Documentation/sp_BlitzIndex_Checks_by_Priority.md index 4da92c72..04ecd466 100644 --- a/Documentation/sp_BlitzIndex_Checks_by_Priority.md +++ b/Documentation/sp_BlitzIndex_Checks_by_Priority.md @@ -6,71 +6,72 @@ Before adding a new check, make sure to add a Github issue for it first, and hav If you want to change anything about a check - the priority, finding, URL, or ID - open a Github issue first. The relevant scripts have to be updated too. -CURRENT HIGH CHECKID: 126 -If you want to add a new check, start at 127. +CURRENT HIGH CHECKID: 127 +If you want to add a new check, start at 128. -| Priority | FindingsGroup | Finding | URL | CheckID | -| -------- | ----------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------- | ------- | -| 10 | Over-Indexing | Many NC Indexes on a Single Table | https://www.brentozar.com/go/IndexHoarder | 20 | -| 10 | Over-Indexing | Unused NC Index with High Writes | https://www.brentozar.com/go/IndexHoarder | 22 | -| 10 | Resumable Indexing | Resumable Index Operation Paused | https://www.BrentOzar.com/go/resumable | 122 | -| 10 | Resumable Indexing | Resumable Index Operation Running | https://www.BrentOzar.com/go/resumable | 123 | -| 20 | Redundant Indexes | Duplicate Keys | https://www.brentozar.com/go/duplicateindex | 1 | -| 30 | Redundant Indexes | Approximate Duplicate Keys | https://www.brentozar.com/go/duplicateindex | 2 | -| 40 | Index Suggestion | High Value Missing Index | https://www.brentozar.com/go/indexaphobia | 50 | -| 70 | Locking-Prone Indexes | Total Lock Time with Long Average Waits | https://www.brentozar.com/go/aggressiveindexes | 11 | -| 70 | Locking-Prone Indexes | Total Lock Time with Short Average Waits | https://www.brentozar.com/go/aggressiveindexes | 12 | -| 80 | Abnormal Design Pattern | Columnstore Indexes with Trace Flag 834 | https://support.microsoft.com/en-us/kb/3210239 | 72 | -| 80 | Abnormal Design Pattern | Identity Column Near End of Range | https://www.brentozar.com/go/AbnormalPsychology | 68 | -| 80 | Abnormal Design Pattern | Filter Columns Not In Index Definition | https://www.brentozar.com/go/IndexFeatures | 34 | -| 80 | Abnormal Design Pattern | History Table With NonClustered Index | https://sqlserverfast.com/blog/hugo/2023/09/an-update-on-merge/ | 124 | -| 90 | Statistics Warnings | Low Sampling Rates | https://www.brentozar.com/go/stats | 91 | -| 90 | Statistics Warnings | Persisted Sampling Rates (Unexpected) | `https://www.youtube.com/watch?v=V5illj_KOJg&t=758s` | 125 | -| 90 | Statistics Warnings | Statistics Not Updated Recently | https://www.brentozar.com/go/stats | 90 | -| 90 | Statistics Warnings | Statistics with NO RECOMPUTE | https://www.brentozar.com/go/stats | 92 | -| 100 | Over-Indexing | NC index with High Writes:Reads | https://www.brentozar.com/go/IndexHoarder | 48 | -| 100 | Indexes Worth Reviewing | Heap with a Nonclustered Primary Key | https://www.brentozar.com/go/SelfLoathing | 47 | -| 100 | Indexes Worth Reviewing | Heap with Forwarded Fetches | https://www.brentozar.com/go/SelfLoathing | 43 | -| 100 | Indexes Worth Reviewing | Large Active Heap | https://www.brentozar.com/go/SelfLoathing | 44 | -| 100 | Indexes Worth Reviewing | Low Fill Factor on Clustered Index | https://www.brentozar.com/go/SelfLoathing | 40 | -| 100 | Indexes Worth Reviewing | Low Fill Factor on Nonclustered Index | https://www.brentozar.com/go/SelfLoathing | 40 | -| 100 | Indexes Worth Reviewing | Medium Active Heap | https://www.brentozar.com/go/SelfLoathing | 45 | -| 100 | Indexes Worth Reviewing | Small Active Heap | https://www.brentozar.com/go/SelfLoathing | 46 | -| 100 | Forced Serialization | Computed Column with Scalar UDF | https://www.brentozar.com/go/serialudf | 99 | -| 100 | Forced Serialization | Check Constraint with Scalar UDF | https://www.brentozar.com/go/computedscalar | 94 | -| 150 | Abnormal Design Pattern | Cascading Updates or Deletes | https://www.brentozar.com/go/AbnormalPsychology | 71 | -| 150 | Abnormal Design Pattern | Unindexed Foreign Keys | https://www.brentozar.com/go/AbnormalPsychology | 72 | -| 150 | Abnormal Design Pattern | Columnstore Index | https://www.brentozar.com/go/AbnormalPsychology | 61 | -| 150 | Abnormal Design Pattern | Column Collation Does Not Match Database Collation | https://www.brentozar.com/go/AbnormalPsychology | 69 | -| 150 | Abnormal Design Pattern | Compressed Index | https://www.brentozar.com/go/AbnormalPsychology | 63 | -| 150 | Abnormal Design Pattern | In-Memory OLTP | https://www.brentozar.com/go/AbnormalPsychology | 73 | -| 150 | Abnormal Design Pattern | Non-Aligned Index on a Partitioned Table | https://www.brentozar.com/go/AbnormalPsychology | 65 | -| 150 | Abnormal Design Pattern | Partitioned Index | https://www.brentozar.com/go/AbnormalPsychology | 64 | -| 150 | Abnormal Design Pattern | Spatial Index | https://www.brentozar.com/go/AbnormalPsychology | 62 | -| 150 | Abnormal Design Pattern | XML Index | https://www.brentozar.com/go/AbnormalPsychology | 60 | -| 150 | Over-Indexing | Approximate: Wide Indexes (7 or More Columns) | https://www.brentozar.com/go/IndexHoarder | 23 | -| 150 | Over-Indexing | More Than 5 Percent NC Indexes Are Unused | https://www.brentozar.com/go/IndexHoarder | 21 | -| 150 | Over-Indexing | Non-Unique Clustered Index | https://www.brentozar.com/go/IndexHoarder | 28 | -| 150 | Over-Indexing | Unused NC Index with Low Writes | https://www.brentozar.com/go/IndexHoarder | 29 | -| 150 | Over-Indexing | Wide Clustered Index (>3 columns or >16 bytes) | https://www.brentozar.com/go/IndexHoarder | 24 | -| 150 | Indexes Worth Reviewing | Disabled Index | https://www.brentozar.com/go/SelfLoathing | 42 | -| 150 | Indexes Worth Reviewing | Hypothetical Index | https://www.brentozar.com/go/SelfLoathing | 41 | -| 200 | Abnormal Design Pattern | Identity Column Using a Negative Seed or Increment Other Than 1 | https://www.brentozar.com/go/AbnormalPsychology | 74 | -| 200 | Abnormal Design Pattern | Recently Created Tables/Indexes (1 week) | https://www.brentozar.com/go/AbnormalPsychology | 66 | -| 200 | Abnormal Design Pattern | Recently Modified Tables/Indexes (2 days) | https://www.brentozar.com/go/AbnormalPsychology | 67 | -| 200 | Abnormal Design Pattern | Replicated Columns | https://www.brentozar.com/go/AbnormalPsychology | 70 | -| 200 | Abnormal Design Pattern | Temporal Tables | https://www.brentozar.com/go/AbnormalPsychology | 110 | -| 200 | Repeated Calculations | Computed Columns Not Persisted | https://www.brentozar.com/go/serialudf | 100 | -| 200 | Statistics Warnings | Statistics With Filters | https://www.brentozar.com/go/stats | 93 | -| 200 | Statistics Warnings | Persisted Sampling Rates (Expected) | `https://www.youtube.com/watch?v=V5illj_KOJg&t=758s` | 126 | -| 200 | Over-Indexing | High Ratio of Nulls | https://www.brentozar.com/go/IndexHoarder | 25 | -| 200 | Over-Indexing | High Ratio of Strings | https://www.brentozar.com/go/IndexHoarder | 27 | -| 200 | Over-Indexing | Wide Tables: 35+ cols or > 2000 non-LOB bytes | https://www.brentozar.com/go/IndexHoarder | 26 | -| 200 | Indexes Worth Reviewing | Heaps with Deletes | https://www.brentozar.com/go/SelfLoathing | 49 | -| 200 | High Workloads | Scan-a-lots (index-usage-stats) | https://www.brentozar.com/go/Workaholics | 80 | -| 200 | High Workloads | Top Recent Accesses (index-op-stats) | https://www.brentozar.com/go/Workaholics | 81 | -| 250 | Omitted Index Features | Few Indexes Use Includes | https://www.brentozar.com/go/IndexFeatures | 31 | -| 250 | Omitted Index Features | No Filtered Indexes or Indexed Views | https://www.brentozar.com/go/IndexFeatures | 32 | -| 250 | Omitted Index Features | No Indexes Use Includes | https://www.brentozar.com/go/IndexFeatures | 30 | -| 250 | Omitted Index Features | Potential Filtered Index (Based on Column Name) | https://www.brentozar.com/go/IndexFeatures | 33 | -| 250 | Specialized Indexes | Optimized For Sequential Keys | | 121 | +| Priority | FindingsGroup | Finding | URL | CheckID | +| -------- | ----------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ------- | +| 10 | Over-Indexing | Many NC Indexes on a Single Table | https://www.brentozar.com/go/IndexHoarder | 20 | +| 10 | Over-Indexing | Unused NC Index with High Writes | https://www.brentozar.com/go/IndexHoarder | 22 | +| 10 | Resumable Indexing | Resumable Index Operation Paused | https://www.BrentOzar.com/go/resumable | 122 | +| 10 | Resumable Indexing | Resumable Index Operation Running | https://www.BrentOzar.com/go/resumable | 123 | +| 20 | Redundant Indexes | Duplicate Keys | https://www.brentozar.com/go/duplicateindex | 1 | +| 30 | Redundant Indexes | Approximate Duplicate Keys | https://www.brentozar.com/go/duplicateindex | 2 | +| 40 | Index Suggestion | High Value Missing Index | https://www.brentozar.com/go/indexaphobia | 50 | +| 70 | Locking-Prone Indexes | Total Lock Time with Long Average Waits | https://www.brentozar.com/go/aggressiveindexes | 11 | +| 70 | Locking-Prone Indexes | Total Lock Time with Short Average Waits | https://www.brentozar.com/go/aggressiveindexes | 12 | +| 80 | Abnormal Design Pattern | Columnstore Indexes with Trace Flag 834 | https://support.microsoft.com/en-us/kb/3210239 | 72 | +| 80 | Abnormal Design Pattern | Identity Column Near End of Range | https://www.brentozar.com/go/AbnormalPsychology | 68 | +| 80 | Abnormal Design Pattern | Filter Columns Not In Index Definition | https://www.brentozar.com/go/IndexFeatures | 34 | +| 80 | Abnormal Design Pattern | History Table With NonClustered Index | https://sqlserverfast.com/blog/hugo/2023/09/an-update-on-merge/ | 124 | +| 90 | Statistics Warnings | Low Sampling Rates | https://www.brentozar.com/go/stats | 91 | +| 90 | Statistics Warnings | Persisted Sampling Rates (Unexpected) | `https://www.youtube.com/watch?v=V5illj_KOJg&t=758s` | 125 | +| 90 | Statistics Warnings | Statistics Not Updated Recently | https://www.brentozar.com/go/stats | 90 | +| 90 | Statistics Warnings | Statistics with NO RECOMPUTE | https://www.brentozar.com/go/stats | 92 | +| 100 | Over-Indexing | NC index with High Writes:Reads | https://www.brentozar.com/go/IndexHoarder | 48 | +| 100 | Indexes Worth Reviewing | Heap with a Nonclustered Primary Key | https://www.brentozar.com/go/SelfLoathing | 47 | +| 100 | Indexes Worth Reviewing | Heap with Forwarded Fetches | https://www.brentozar.com/go/SelfLoathing | 43 | +| 100 | Indexes Worth Reviewing | Large Active Heap | https://www.brentozar.com/go/SelfLoathing | 44 | +| 100 | Indexes Worth Reviewing | Low Fill Factor on Clustered Index | https://www.brentozar.com/go/SelfLoathing | 40 | +| 100 | Indexes Worth Reviewing | Low Fill Factor on Nonclustered Index | https://www.brentozar.com/go/SelfLoathing | 40 | +| 100 | Indexes Worth Reviewing | Medium Active Heap | https://www.brentozar.com/go/SelfLoathing | 45 | +| 100 | Indexes Worth Reviewing | Small Active Heap | https://www.brentozar.com/go/SelfLoathing | 46 | +| 100 | Forced Serialization | Computed Column with Scalar UDF | https://www.brentozar.com/go/serialudf | 99 | +| 100 | Forced Serialization | Check Constraint with Scalar UDF | https://www.brentozar.com/go/computedscalar | 94 | +| 150 | Abnormal Design Pattern | Cascading Updates or Deletes | https://www.brentozar.com/go/AbnormalPsychology | 71 | +| 150 | Abnormal Design Pattern | Unindexed Foreign Keys | https://www.brentozar.com/go/AbnormalPsychology | 72 | +| 150 | Abnormal Design Pattern | Columnstore Index | https://www.brentozar.com/go/AbnormalPsychology | 61 | +| 150 | Abnormal Design Pattern | Column Collation Does Not Match Database Collation | https://www.brentozar.com/go/AbnormalPsychology | 69 | +| 150 | Abnormal Design Pattern | Compressed Index | https://www.brentozar.com/go/AbnormalPsychology | 63 | +| 150 | Abnormal Design Pattern | In-Memory OLTP | https://www.brentozar.com/go/AbnormalPsychology | 73 | +| 150 | Abnormal Design Pattern | Non-Aligned Index on a Partitioned Table | https://www.brentozar.com/go/AbnormalPsychology | 65 | +| 150 | Abnormal Design Pattern | Partitioned Index | https://www.brentozar.com/go/AbnormalPsychology | 64 | +| 150 | Abnormal Design Pattern | Spatial Index | https://www.brentozar.com/go/AbnormalPsychology | 62 | +| 150 | Abnormal Design Pattern | XML Index | https://www.brentozar.com/go/AbnormalPsychology | 60 | +| 150 | Over-Indexing | Approximate: Wide Indexes (7 or More Columns) | https://www.brentozar.com/go/IndexHoarder | 23 | +| 150 | Over-Indexing | More Than 5 Percent NC Indexes Are Unused | https://www.brentozar.com/go/IndexHoarder | 21 | +| 150 | Over-Indexing | Non-Unique Clustered Index | https://www.brentozar.com/go/IndexHoarder | 28 | +| 150 | Over-Indexing | Unused NC Index with Low Writes | https://www.brentozar.com/go/IndexHoarder | 29 | +| 150 | Over-Indexing | Wide Clustered Index (>3 columns or >16 bytes) | https://www.brentozar.com/go/IndexHoarder | 24 | +| 150 | Indexes Worth Reviewing | Disabled Index | https://www.brentozar.com/go/SelfLoathing | 42 | +| 150 | Indexes Worth Reviewing | Hypothetical Index | https://www.brentozar.com/go/SelfLoathing | 41 | +| 200 | Abnormal Design Pattern | Identity Column Using a Negative Seed or Increment Other Than 1 | https://www.brentozar.com/go/AbnormalPsychology | 74 | +| 200 | Abnormal Design Pattern | Recently Created Tables/Indexes (1 week) | https://www.brentozar.com/go/AbnormalPsychology | 66 | +| 200 | Abnormal Design Pattern | Recently Modified Tables/Indexes (2 days) | https://www.brentozar.com/go/AbnormalPsychology | 67 | +| 200 | Abnormal Design Pattern | Replicated Columns | https://www.brentozar.com/go/AbnormalPsychology | 70 | +| 200 | Abnormal Design Pattern | Temporal Tables | https://www.brentozar.com/go/AbnormalPsychology | 110 | +| 200 | Repeated Calculations | Computed Columns Not Persisted | https://www.brentozar.com/go/serialudf | 100 | +| 200 | Statistics Warnings | Statistics With Filters | https://www.brentozar.com/go/stats | 93 | +| 200 | Statistics Warnings | Persisted Sampling Rates (Expected) | `https://www.youtube.com/watch?v=V5illj_KOJg&t=758s` | 126 | +| 200 | Statistics Warnings | Partitioned Table Without Incremental Statistics | https://sqlperformance.com/2015/05/sql-statistics/improving-maintenance-incremental-statistics | 127 | +| 200 | Over-Indexing | High Ratio of Nulls | https://www.brentozar.com/go/IndexHoarder | 25 | +| 200 | Over-Indexing | High Ratio of Strings | https://www.brentozar.com/go/IndexHoarder | 27 | +| 200 | Over-Indexing | Wide Tables: 35+ cols or > 2000 non-LOB bytes | https://www.brentozar.com/go/IndexHoarder | 26 | +| 200 | Indexes Worth Reviewing | Heaps with Deletes | https://www.brentozar.com/go/SelfLoathing | 49 | +| 200 | High Workloads | Scan-a-lots (index-usage-stats) | https://www.brentozar.com/go/Workaholics | 80 | +| 200 | High Workloads | Top Recent Accesses (index-op-stats) | https://www.brentozar.com/go/Workaholics | 81 | +| 250 | Omitted Index Features | Few Indexes Use Includes | https://www.brentozar.com/go/IndexFeatures | 31 | +| 250 | Omitted Index Features | No Filtered Indexes or Indexed Views | https://www.brentozar.com/go/IndexFeatures | 32 | +| 250 | Omitted Index Features | No Indexes Use Includes | https://www.brentozar.com/go/IndexFeatures | 30 | +| 250 | Omitted Index Features | Potential Filtered Index (Based on Column Name) | https://www.brentozar.com/go/IndexFeatures | 33 | +| 250 | Specialized Indexes | Optimized For Sequential Keys | | 121 | diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 56d83b58..078d5c5d 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -726,6 +726,7 @@ IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL CREATE TABLE #Statistics ( database_id INT NOT NULL, database_name NVARCHAR(256) NOT NULL, + object_id INT NOT NULL, table_name NVARCHAR(128) NULL, schema_name NVARCHAR(128) NULL, index_name NVARCHAR(128) NULL, @@ -746,7 +747,8 @@ IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL no_recompute BIT NULL, has_filter BIT NULL, filter_definition NVARCHAR(MAX) NULL, - persisted_sample_percent FLOAT NULL + persisted_sample_percent FLOAT NULL, + is_incremental BIT NULL ); CREATE TABLE #ComputedColumns @@ -2284,14 +2286,15 @@ OPTION (RECOMPILE);'; BEGIN RAISERROR (N'Gathering Statistics Info With Newer Syntax.',0,1) WITH NOWAIT; SET @dsql=N'USE ' + QUOTENAME(@DatabaseName) + N'; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT #Statistics ( database_id, database_name, table_name, schema_name, index_name, column_names, statistics_name, last_statistics_update, + INSERT #Statistics ( database_id, database_name, object_id, table_name, schema_name, index_name, column_names, statistics_name, last_statistics_update, days_since_last_stats_update, rows, rows_sampled, percent_sampled, histogram_steps, modification_counter, percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date, - no_recompute, has_filter, filter_definition, persisted_sample_percent) + no_recompute, has_filter, filter_definition, persisted_sample_percent, is_incremental) SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], - @i_DatabaseName AS database_name, - obj.name AS table_name, - sch.name AS schema_name, + @i_DatabaseName AS database_name, + obj.object_id, + obj.name AS table_name, + sch.name AS schema_name, ISNULL(i.name, ''System Or User Statistic'') AS index_name, ca.column_names AS column_names, s.name AS statistics_name, @@ -2323,8 +2326,16 @@ OPTION (RECOMPILE);'; FROM sys.all_columns AS all_cols WHERE all_cols.[object_id] = OBJECT_ID(N'sys.dm_db_stats_properties', N'IF') AND all_cols.[name] = N'persisted_sample_percent' ) - THEN N'ddsp.persisted_sample_percent' - ELSE N'NULL AS persisted_sample_percent' END + THEN N'ddsp.persisted_sample_percent,' + ELSE N'NULL AS persisted_sample_percent,' END + + CASE WHEN EXISTS + ( + SELECT 1 + FROM sys.all_columns AS all_cols + WHERE all_cols.[object_id] = OBJECT_ID(N'sys.stats', N'V') AND all_cols.[name] = N'is_incremental' + ) + THEN N's.is_incremental' + ELSE N'NULL AS is_incremental' END + N' FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj @@ -2370,12 +2381,13 @@ OPTION (RECOMPILE);'; BEGIN RAISERROR (N'Gathering Statistics Info With Older Syntax.',0,1) WITH NOWAIT; SET @dsql=N'USE ' + QUOTENAME(@DatabaseName) + N'; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT #Statistics(database_id, database_name, table_name, schema_name, index_name, column_names, statistics_name, + INSERT #Statistics(database_id, database_name, object_id, table_name, schema_name, index_name, column_names, statistics_name, last_statistics_update, days_since_last_stats_update, rows, modification_counter, percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date, - no_recompute, has_filter, filter_definition, persisted_sample_percent) + no_recompute, has_filter, filter_definition, persisted_sample_percent, is_incremental) SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], @i_DatabaseName AS database_name, + obj.object_id, obj.name AS table_name, sch.name AS schema_name, ISNULL(i.name, ''System Or User Statistic'') AS index_name, @@ -2402,7 +2414,16 @@ OPTION (RECOMPILE);'; ELSE N'NULL AS has_filter, NULL AS filter_definition,' END /* Certainly NULL. This branch does not even join on the table that this column comes from. */ - + N'NULL AS persisted_sample_percent' + + N'NULL AS persisted_sample_percent, + ' + + CASE WHEN EXISTS + ( + SELECT 1 + FROM sys.all_columns AS all_cols + WHERE all_cols.[object_id] = OBJECT_ID(N'sys.stats', N'V') AND all_cols.[name] = N'is_incremental' + ) + THEN N's.is_incremental' + ELSE N'NULL AS is_incremental' END + N' FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.sysindexes si @@ -5672,7 +5693,49 @@ BEGIN GROUP BY s.database_name OPTION ( RECOMPILE ); - + RAISERROR(N'check_id 127: Partitioned Table Without Incremental Statistics', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary, more_info ) + SELECT 127 AS check_id, + 200 AS Priority, + 'Statistics Warnings' AS findings_group, + 'Partitioned Table Without Incremental Statistics', + partitioned_tables.database_name, + 'https://sqlperformance.com/2015/05/sql-statistics/improving-maintenance-incremental-statistics' AS URL, + 'The table ' + QUOTENAME(partitioned_tables.schema_name) + '.' + QUOTENAME(partitioned_tables.object_name) + ' is partitioned, but ' + + CONVERT(NVARCHAR(100), incremental_stats_counts.not_incremental_stats_count) + ' of its ' + CONVERT(NVARCHAR(100), incremental_stats_counts.stats_count) + + ' statistics are not incremental. If this is a sliding/rolling window table, then consider making the statistics incremental. If not, then investigate why this table is partitioned.' AS details, + partitioned_tables.object_name + N' (Entire table)' AS index_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary, + partitioned_tables.more_info + FROM + ( + SELECT s.database_id, + s.object_id, + COUNT(CASE WHEN s.is_incremental = 0 THEN 1 END) AS not_incremental_stats_count, + COUNT(*) AS stats_count + FROM #Statistics AS s + GROUP BY s.database_id, s.object_id + HAVING COUNT(CASE WHEN s.is_incremental = 0 THEN 1 END) > 0 + ) AS incremental_stats_counts + JOIN + ( + /* Just get the tables. We do not need the indexes. */ + SELECT DISTINCT i.database_name, + i.database_id, + i.object_id, + i.schema_name, + i.object_name, + /* This is a little bit dishonest, since it tells us nothing about if the statistics are incremental. */ + i.more_info + FROM #IndexSanity AS i + WHERE i.partition_key_column_name IS NOT NULL + ) AS partitioned_tables + ON partitioned_tables.database_id = incremental_stats_counts.database_id AND partitioned_tables.object_id = incremental_stats_counts.object_id + /* No need for a GROUP BY. What we are joining on has exactly one row in each sub-query. */ + OPTION ( RECOMPILE ); END /* IF @Mode = 4 */ From b32c61ad255064ed2198600791a4da73b1d4e2c6 Mon Sep 17 00:00:00 2001 From: Eilandor Date: Thu, 4 Sep 2025 16:55:52 +0300 Subject: [PATCH 22/27] minor typo in sp_BlitzFirst.sql noticed a small typo in your last video. --- sp_BlitzFirst.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index bfcb66bd..df1dd7d0 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2636,7 +2636,7 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, 'Server Performance' AS FindingGroup, 'Memory Dangerously Low Recently' AS Finding, 'https://www.brentozar.com/go/memhist' AS URL, - N'As recently as ' + CONVERT(NVARCHAR(19), snapshot_time, 120) + N', memory health issues are being reported in sys.dm_os_memory_health, indicating extreme memory pressure.' AS Details + N'As recently as ' + CONVERT(NVARCHAR(19), snapshot_time, 120) + N', memory health issues are being reported in sys.dm_os_memory_health_history, indicating extreme memory pressure.' AS Details FROM sys.dm_os_memory_health_history WHERE severity_level > 1; END From ee7c4d31bb4b82ba573848f19ff8a6e37fb90812 Mon Sep 17 00:00:00 2001 From: ReeceGoding <67124261+ReeceGoding@users.noreply.github.com> Date: Thu, 4 Sep 2025 19:59:38 +0100 Subject: [PATCH 23/27] Made modifications_before_auto_update a BIGINT. Closes #3701 but I cannot test it. --- sp_BlitzIndex.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 078d5c5d..67eb3c7d 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -740,7 +740,7 @@ IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL histogram_steps INT NULL, modification_counter BIGINT NULL, percent_modifications DECIMAL(18, 1) NULL, - modifications_before_auto_update INT NULL, + modifications_before_auto_update BIGINT NULL, index_type_desc NVARCHAR(128) NULL, table_create_date DATETIME NULL, table_modify_date DATETIME NULL, @@ -2310,7 +2310,7 @@ OPTION (RECOMPILE);'; ELSE ddsp.modification_counter END AS percent_modifications, CASE WHEN ddsp.rows < 500 THEN 500 - ELSE CAST(( ddsp.rows * .20 ) + 500 AS INT) + ELSE CAST(( ddsp.rows * .20 ) + 500 AS BIGINT) END AS modifications_before_auto_update, ISNULL(i.type_desc, ''System Or User Statistic - N/A'') AS index_type_desc, CONVERT(DATETIME, obj.create_date) AS table_create_date, @@ -2401,7 +2401,7 @@ OPTION (RECOMPILE);'; ELSE si.rowmodctr END AS percent_modifications, CASE WHEN si.rowcnt < 500 THEN 500 - ELSE CAST(( si.rowcnt * .20 ) + 500 AS INT) + ELSE CAST(( si.rowcnt * .20 ) + 500 AS BIGINT) END AS modifications_before_auto_update, ISNULL(i.type_desc, ''System Or User Statistic - N/A'') AS index_type_desc, CONVERT(DATETIME, obj.create_date) AS table_create_date, From 3cff8339eea16144616341bf44a390a3dba02bac Mon Sep 17 00:00:00 2001 From: Bruce Wilson Date: Mon, 8 Sep 2025 14:18:16 -0500 Subject: [PATCH 24/27] Tests in sp_BlitzIndex to exclude indexes created within 7 days or modified within 2 days were reversed, resulting in ONLY reporting on very recent indexes. --- sp_BlitzIndex.sql | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 078d5c5d..40044171 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -4671,9 +4671,9 @@ BEGIN JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE index_id NOT IN ( 0, 1 ) AND i.is_unique = 0 - /*Skipping tables created in the last week, or modified in past 2 days*/ - AND i.create_date >= DATEADD(dd,-7,GETDATE()) - AND i.modify_date > DATEADD(dd,-2,GETDATE()) + /*Skipping tables created in the last week, or modified in past 2 days*/ + AND i.create_date < DATEADD(dd,-7,GETDATE()) + AND i.modify_date < DATEADD(dd,-2,GETDATE()) OPTION ( RECOMPILE ); IF @percent_NC_indexes_unused >= 5 INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -4704,9 +4704,9 @@ BEGIN WHERE index_id NOT IN ( 0, 1 ) AND i.is_unique = 0 AND total_reads = 0 - /*Skipping tables created in the last week, or modified in past 2 days*/ - AND i.create_date >= DATEADD(dd,-7,GETDATE()) - AND i.modify_date > DATEADD(dd,-2,GETDATE()) + /*Skipping tables created in the last week, or modified in past 2 days*/ + AND i.create_date < DATEADD(dd,-7,GETDATE()) + AND i.modify_date < DATEADD(dd,-2,GETDATE()) GROUP BY i.database_name OPTION ( RECOMPILE ); @@ -4957,9 +4957,9 @@ BEGIN AND i.index_id NOT IN (0,1) /*NCs only*/ AND i.is_unique = 0 AND sz.total_reserved_MB >= CASE WHEN (@GetAllDatabases = 1 OR @Mode = 0) THEN @ThresholdMB ELSE sz.total_reserved_MB END - /*Skipping tables created in the last week, or modified in past 2 days*/ - AND i.create_date >= DATEADD(dd,-7,GETDATE()) - AND i.modify_date > DATEADD(dd,-2,GETDATE()) + /*Skipping tables created in the last week, or modified in past 2 days*/ + AND i.create_date < DATEADD(dd,-7,GETDATE()) + AND i.modify_date < DATEADD(dd,-2,GETDATE()) ORDER BY i.db_schema_object_indexid OPTION ( RECOMPILE ); From 0e3c131f62047068487d163c1f6d613e814cf417 Mon Sep 17 00:00:00 2001 From: Tom Willwerth Date: Sun, 14 Sep 2025 15:47:39 -0400 Subject: [PATCH 25/27] A few improvements for Linux --- sp_Blitz.sql | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sp_Blitz.sql b/sp_Blitz.sql index 48ab2efe..b0d36012 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -5013,7 +5013,7 @@ IF @ProductVersionMajor >= 10 END; END; /* CheckID 258 - Security - SQL Server Service is running as LocalSystem or NT AUTHORITY\SYSTEM */ -IF @ProductVersionMajor >= 10 +IF (@ProductVersionMajor >= 10 AND @IsWindowsOperatingSystem = 1) AND NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 258 ) @@ -5050,7 +5050,7 @@ IF @ProductVersionMajor >= 10 END; /* CheckID 259 - Security - SQL Server Agent Service is running as LocalSystem or NT AUTHORITY\SYSTEM */ -IF @ProductVersionMajor >= 10 +IF (@ProductVersionMajor >= 10 AND @IsWindowsOperatingSystem = 1) AND NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 259 ) @@ -5123,7 +5123,7 @@ IF @ProductVersionMajor >= 10 END; /*This checks which service account SQL Server is running as.*/ -IF @ProductVersionMajor >= 10 +IF (@ProductVersionMajor >= 10 AND @IsWindowsOperatingSystem = 1) AND NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 169 ) @@ -5163,7 +5163,7 @@ IF @ProductVersionMajor >= 10 END; /*This checks which service account SQL Agent is running as.*/ -IF @ProductVersionMajor >= 10 +IF (@ProductVersionMajor >= 10 AND @IsWindowsOperatingSystem = 1) AND NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 170 ) @@ -9233,7 +9233,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 ''Server Info'' AS FindingsGroup , ''Services'' AS Finding , '''' AS URL , - N''Service: '' + servicename + N'' runs under service account '' + service_account + N''. Last startup time: '' + COALESCE(CAST(CASE WHEN YEAR(last_startup_time) <= 1753 THEN CAST(''17530101'' as datetime) ELSE CAST(last_startup_time AS DATETIME) END AS VARCHAR(50)), ''not shown.'') + ''. Startup type: '' + startup_type_desc + N'', currently '' + status_desc + ''.'' + N''Service: '' + servicename + ISNULL((N'' runs under service account '' + service_account),'''') + N''. Last startup time: '' + COALESCE(CAST(CASE WHEN YEAR(last_startup_time) <= 1753 THEN CAST(''17530101'' as datetime) ELSE CAST(last_startup_time AS DATETIME) END AS VARCHAR(50)), ''not shown.'') + ''. Startup type: '' + startup_type_desc + N'', currently '' + status_desc + ''.'' FROM sys.dm_server_services OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; From 0f824b0e0afd967cbf2a14ff541e20ae270e4df7 Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 25 Sep 2025 04:54:51 -0700 Subject: [PATCH 26/27] 3708_BlitzFirst_MI_operations Added check 53 for Azure operations ongoing. --- .../sp_BlitzFirst_Checks_by_Priority.md | 5 +++-- sp_BlitzFirst.sql | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Documentation/sp_BlitzFirst_Checks_by_Priority.md b/Documentation/sp_BlitzFirst_Checks_by_Priority.md index 36d2bbe7..433d096f 100644 --- a/Documentation/sp_BlitzFirst_Checks_by_Priority.md +++ b/Documentation/sp_BlitzFirst_Checks_by_Priority.md @@ -6,8 +6,8 @@ Before adding a new check, make sure to add a Github issue for it first, and hav If you want to change anything about a check - the priority, finding, URL, or ID - open a Github issue first. The relevant scripts have to be updated too. -CURRENT HIGH CHECKID: 52 -If you want to add a new check, start at 53. +CURRENT HIGH CHECKID: 53 +If you want to add a new check, start at 54. | Priority | FindingsGroup | Finding | URL | CheckID | |----------|---------------------------------|---------------------------------------|-------------------------------------------------|----------| @@ -36,6 +36,7 @@ If you want to add a new check, start at 53. | 50 | Query Problems | Re-Compilations/Sec High | https://www.brentozar.com/go/recompile | 16 | | 50 | Query Problems | Statistics Updated Recently | https://www.brentozar.com/go/stats | 44 | | 50 | Query Problems | High Percentage Of Runnable Queries | https://erikdarlingdata.com/go/RunnableQueue/ | 47 | +| 50 | Server Performance | Azure Operation Ongoing | https://learn.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-operation-status-azure-sql-database | 53 | | 50 | Server Performance | High CPU Utilization | https://www.brentozar.com/go/cpu | 24 | | 50 | Server Performance | High CPU Utilization - Non SQL Processes | https://www.brentozar.com/go/cpu | 28 | | 50 | Server Performance | Slow Data File Reads | https://www.brentozar.com/go/slow | 11 | diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index df1dd7d0..91245229 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -2592,6 +2592,27 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END + /* Server Performance - Azure Operation Ongoing - CheckID 53 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 53',10,1) WITH NOWAIT; + END + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_operation_status') + BEGIN + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT 53 AS CheckID, + 50 AS Priority, + 'Server Performance' AS FindingGroup, + 'Azure Operation ' + CASE WHEN state IN (2, 3, 5) THEN 'Ended Recently' ELSE 'Ongoing' END AS Finding, + 'https://learn.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-operation-status-azure-sql-database' AS URL, + N'Operation: ' + operation + N' State: ' + state_desc + N' Percent Complete: ' + CAST(percent_complete AS NVARCHAR(10)) + @LineFeed + + N' On: ' + CAST(resource_type_desc AS NVARCHAR(100)) + N':' + CAST(major_resource_id AS NVARCHAR(100)) + @LineFeed + + N' Started: ' + CAST(start_time AS NVARCHAR(100)) + N' Last Modified Time: ' + CAST(last_modify_time AS NVARCHAR(100)) + @LineFeed + + N' For more information, query SELECT * FROM sys.dm_operation_status; ' AS Details + FROM sys.dm_operation_status + END + + /* Potential Upcoming Problems - High Number of Connections - CheckID 49 */ IF (@Debug = 1) BEGIN From ccfafbc63db567d47c7771e170e400633b190b7e Mon Sep 17 00:00:00 2001 From: Brent Ozar Date: Thu, 2 Oct 2025 09:44:23 -0400 Subject: [PATCH 27/27] 2025-10-02 Release Bumping version numbers and dates, adding releases. --- Install-All-Scripts.sql | 404 +++++++++++++++++++++++++++++++++------- Install-Azure.sql | 298 +++++++++++++++++++++++------ SqlServerVersions.sql | 12 +- sp_Blitz.sql | 2 +- sp_BlitzAnalysis.sql | 2 +- sp_BlitzBackups.sql | 2 +- sp_BlitzCache.sql | 2 +- sp_BlitzFirst.sql | 2 +- sp_BlitzIndex.sql | 2 +- sp_BlitzLock.sql | 2 +- sp_BlitzWho.sql | 2 +- sp_DatabaseRestore.sql | 2 +- sp_ineachdb.sql | 2 +- 13 files changed, 599 insertions(+), 135 deletions(-) diff --git a/Install-All-Scripts.sql b/Install-All-Scripts.sql index 5f310717..f1ec7b55 100644 --- a/Install-All-Scripts.sql +++ b/Install-All-Scripts.sql @@ -26,6 +26,7 @@ ALTER PROCEDURE [dbo].[sp_Blitz] @SummaryMode TINYINT = 0 , @BringThePain TINYINT = 0 , @UsualDBOwner sysname = NULL , + @UsualOwnerOfJobs sysname = NULL , -- This is to set the owner of Jobs is you have a different account than SA that you use as Default @SkipBlockingChecks TINYINT = 1 , @Debug TINYINT = 0 , @Version VARCHAR(30) = NULL OUTPUT, @@ -38,7 +39,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.25', @VersionDate = '20250704'; + SELECT @Version = '8.26', @VersionDate = '20251002'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -864,12 +865,17 @@ AS INSERT INTO #SkipChecks (CheckID) VALUES (6); /* Security - Jobs Owned By Users per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ INSERT INTO #SkipChecks (CheckID) VALUES (21); /* Informational - Database Encrypted per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ INSERT INTO #SkipChecks (CheckID) VALUES (24); /* File Configuration - System Database on C Drive per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ + INSERT INTO #SkipChecks (CheckID) VALUES (30); /* SQL Agent Alerts cannot be configured on MI */ INSERT INTO #SkipChecks (CheckID) VALUES (50); /* Max Server Memory Set Too High - because they max it out */ INSERT INTO #SkipChecks (CheckID) VALUES (55); /* Security - Database Owner <> sa per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */ + INSERT INTO #SkipChecks (CheckID) VALUES (61); /* SQL Agent Alerts cannot be configured on MI */ + INSERT INTO #SkipChecks (CheckID) VALUES (73); /* SQL Agent Failsafe Operator cannot be configured on MI */ INSERT INTO #SkipChecks (CheckID) VALUES (74); /* TraceFlag On - because Azure Managed Instances go wild and crazy with the trace flags */ + INSERT INTO #SkipChecks (CheckID) VALUES (96); /* SQL Agent Alerts cannot be configured on MI */ INSERT INTO #SkipChecks (CheckID) VALUES (97); /* Unusual SQL Server Edition */ INSERT INTO #SkipChecks (CheckID) VALUES (100); /* Remote DAC disabled - but it's working anyway, details here: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1481 */ INSERT INTO #SkipChecks (CheckID) VALUES (186); /* MSDB Backup History Purged Too Frequently */ + INSERT INTO #SkipChecks (CheckID) VALUES (192); /* IFI can not be set for data files and is always used for log files in MI */ INSERT INTO #SkipChecks (CheckID) VALUES (199); /* Default trace, details here: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1481 */ INSERT INTO #SkipChecks (CheckID) VALUES (211); /*Power Plan */ INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'master'); /* Max file size set */ @@ -1932,7 +1938,11 @@ AS BEGIN IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 6) WITH NOWAIT; + + IF @UsualOwnerOfJobs IS NULL + SET @UsualOwnerOfJobs = SUSER_SNAME(0x01); + INSERT INTO #BlitzResults ( CheckID , Priority , @@ -1951,7 +1961,7 @@ AS + '] - meaning if their login is disabled or not available due to Active Directory problems, the job will stop working.' ) AS Details FROM msdb.dbo.sysjobs j WHERE j.enabled = 1 - AND SUSER_SNAME(j.owner_sid) <> SUSER_SNAME(0x01); + AND SUSER_SNAME(j.owner_sid) <> @UsualOwnerOfJobs; END; /* --TOURSTOP06-- */ @@ -3926,6 +3936,38 @@ AS AND SUM([wait_time_ms]) > 60000; END; + IF NOT EXISTS ( SELECT 1 + FROM #SkipChecks + WHERE DatabaseName IS NULL AND CheckID = 270 ) + AND EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_os_memory_health_history') + BEGIN + + IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 270) WITH NOWAIT; + + INSERT INTO #BlitzResults + ( CheckID , + Priority , + FindingsGroup , + Finding , + URL , + Details + ) + SELECT 270 AS CheckID , + 1 AS Priority , + 'Performance' AS FindingGroup , + 'Memory Dangerous Low Recently' AS Finding , + 'https://www.brentozar.com/go/memhist' AS URL , + CAST(SUM(1) AS NVARCHAR(10)) + N' instances of ' + CAST(severity_level_desc AS NVARCHAR(100)) + + N' severity level memory issues reported in the last 4 hours in sys.dm_os_memory_health_history.' + FROM sys.dm_os_memory_health_history + WHERE severity_level > 1 + GROUP BY severity_level, severity_level_desc; + END; + + + + + IF NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 121 ) @@ -4971,7 +5013,7 @@ IF @ProductVersionMajor >= 10 END; END; /* CheckID 258 - Security - SQL Server Service is running as LocalSystem or NT AUTHORITY\SYSTEM */ -IF @ProductVersionMajor >= 10 +IF (@ProductVersionMajor >= 10 AND @IsWindowsOperatingSystem = 1) AND NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 258 ) @@ -5008,7 +5050,7 @@ IF @ProductVersionMajor >= 10 END; /* CheckID 259 - Security - SQL Server Agent Service is running as LocalSystem or NT AUTHORITY\SYSTEM */ -IF @ProductVersionMajor >= 10 +IF (@ProductVersionMajor >= 10 AND @IsWindowsOperatingSystem = 1) AND NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 259 ) @@ -5081,7 +5123,7 @@ IF @ProductVersionMajor >= 10 END; /*This checks which service account SQL Server is running as.*/ -IF @ProductVersionMajor >= 10 +IF (@ProductVersionMajor >= 10 AND @IsWindowsOperatingSystem = 1) AND NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 169 ) @@ -5121,7 +5163,7 @@ IF @ProductVersionMajor >= 10 END; /*This checks which service account SQL Agent is running as.*/ -IF @ProductVersionMajor >= 10 +IF (@ProductVersionMajor >= 10 AND @IsWindowsOperatingSystem = 1) AND NOT EXISTS ( SELECT 1 FROM #SkipChecks WHERE DatabaseName IS NULL AND CheckID = 170 ) @@ -9191,7 +9233,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 ''Server Info'' AS FindingsGroup , ''Services'' AS Finding , '''' AS URL , - N''Service: '' + servicename + N'' runs under service account '' + service_account + N''. Last startup time: '' + COALESCE(CAST(CASE WHEN YEAR(last_startup_time) <= 1753 THEN CAST(''17530101'' as datetime) ELSE CAST(last_startup_time AS DATETIME) END AS VARCHAR(50)), ''not shown.'') + ''. Startup type: '' + startup_type_desc + N'', currently '' + status_desc + ''.'' + N''Service: '' + servicename + ISNULL((N'' runs under service account '' + service_account),'''') + N''. Last startup time: '' + COALESCE(CAST(CASE WHEN YEAR(last_startup_time) <= 1753 THEN CAST(''17530101'' as datetime) ELSE CAST(last_startup_time AS DATETIME) END AS VARCHAR(50)), ''not shown.'') + ''. Startup type: '' + startup_type_desc + N'', currently '' + status_desc + ''.'' FROM sys.dm_server_services OPTION (RECOMPILE);'; IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute; @@ -9867,7 +9909,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 WHERE LOWER(cmdshell_output) = ( SELECT LOWER([service_account]) FROM [sys].[dm_server_services] WHERE [servicename] LIKE 'SQL Server%' - AND [servicename] NOT LIKE 'SQL Server Agent%' + AND [servicename] NOT LIKE 'SQL Server%Agent%' AND [servicename] NOT LIKE 'SQL Server Launchpad%')) BEGIN INSERT INTO #BlitzResults @@ -9913,7 +9955,7 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 FROM #localadmins WHERE LOWER(cmdshell_output) = ( SELECT LOWER([service_account]) FROM [sys].[dm_server_services] - WHERE [servicename] LIKE 'SQL Server Agent%' + WHERE [servicename] LIKE 'SQL Server%Agent%' AND [servicename] NOT LIKE 'SQL Server Launchpad%')) BEGIN INSERT INTO #BlitzResults @@ -9939,14 +9981,23 @@ IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1 /*had to use a different table name because SQL Server/SSMS complains when parsing that the table still exists when it gets to the create part*/ IF OBJECT_ID('tempdb..#localadminsag') IS NOT NULL DROP TABLE #localadminsag; CREATE TABLE #localadminsag (cmdshell_output NVARCHAR(1000)); - INSERT INTO #localadmins - EXEC /**/xp_cmdshell/**/ N'net localgroup administrators' /* added comments around command since some firewalls block this string TL 20210221 */ + /* language specific call of xp cmdshell */ + IF (SELECT os_language_version FROM sys.dm_os_windows_info) = 1031 /* os language code for German. Again, this is a very specific fix, see #3673 */ + BEGIN + INSERT INTO #localadminsag + EXEC /**/xp_cmdshell/**/ N'net localgroup Administratoren' /* german */ + END + ELSE + BEGIN + INSERT INTO #localadminsag + EXEC /**/xp_cmdshell/**/ N'net localgroup administrators' /* added comments around command since some firewalls block this string TL 20210221 */ + END IF EXISTS (SELECT 1 - FROM #localadmins + FROM #localadminsag WHERE LOWER(cmdshell_output) = ( SELECT LOWER([service_account]) FROM [sys].[dm_server_services] - WHERE [servicename] LIKE 'SQL Server Agent%' + WHERE [servicename] LIKE 'SQL Server%Agent%' AND [servicename] NOT LIKE 'SQL Server Launchpad%')) BEGIN INSERT INTO #BlitzResults @@ -10566,7 +10617,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.25', @VersionDate = '20250704'; +SELECT @Version = '8.26', @VersionDate = '20251002'; IF(@VersionCheckMode = 1) BEGIN @@ -11444,7 +11495,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.25', @VersionDate = '20250704'; + SELECT @Version = '8.26', @VersionDate = '20251002'; IF(@VersionCheckMode = 1) BEGIN @@ -13228,7 +13279,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.25', @VersionDate = '20250704'; +SELECT @Version = '8.26', @VersionDate = '20251002'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -17017,12 +17068,12 @@ SELECT @@SPID AS SPID, AND ci.comma_paren_charindex > 0 THEN SUBSTRING(ci.expression, ci.paren_charindex, ci.comma_paren_charindex) END AS converted_to, - CASE WHEN ci.at_charindex = 0 + LEFT(CASE WHEN ci.at_charindex = 0 AND ci.convert_implicit_charindex = 0 AND ci.proc_name = 'Statement' THEN SUBSTRING(ci.expression, ci.equal_charindex, 4000) ELSE '**idk_man**' - END AS compile_time_value + END, 258) AS compile_time_value FROM #conversion_info AS ci OPTION (RECOMPILE); @@ -20588,6 +20639,7 @@ ALTER PROCEDURE dbo.sp_BlitzIndex /*Note:@Filter doesn't do anything unless @Mode=0*/ @SkipPartitions BIT = 0, @SkipStatistics BIT = 1, + @UsualStatisticsSamplingPercent FLOAT = 100, /* FLOAT to match sys.dm_db_stats_properties. More detail later. 100 by default because Brent suggests that if people are persisting statistics at all, they are probably doing 100 in lots of places and not filtering that out would produce noise. */ @GetAllDatabases BIT = 0, @ShowColumnstoreOnly BIT = 0, /* Will show only the Row Group and Segment details for a table with a columnstore index. */ @BringThePain BIT = 0, @@ -20614,7 +20666,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.25', @VersionDate = '20250704'; +SELECT @Version = '8.26', @VersionDate = '20251002'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -20739,6 +20791,12 @@ BEGIN RETURN; END; +IF(@UsualStatisticsSamplingPercent <= 0 OR @UsualStatisticsSamplingPercent > 100) +BEGIN + RAISERROR('Invalid value for parameter @UsualStatisticsSamplingPercent. Expected: 1 to 100',12,1); + RETURN; +END; + IF(@OutputType = 'TABLE' AND NOT (@OutputTableName IS NULL AND @OutputSchemaName IS NULL AND @OutputDatabaseName IS NULL AND @OutputServerName IS NULL)) BEGIN RAISERROR(N'One or more output parameters specified in combination with TABLE output, changing to NONE output mode', 0,1) WITH NOWAIT; @@ -21284,6 +21342,7 @@ IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL CREATE TABLE #Statistics ( database_id INT NOT NULL, database_name NVARCHAR(256) NOT NULL, + object_id INT NOT NULL, table_name NVARCHAR(128) NULL, schema_name NVARCHAR(128) NULL, index_name NVARCHAR(128) NULL, @@ -21297,13 +21356,15 @@ IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL histogram_steps INT NULL, modification_counter BIGINT NULL, percent_modifications DECIMAL(18, 1) NULL, - modifications_before_auto_update INT NULL, + modifications_before_auto_update BIGINT NULL, index_type_desc NVARCHAR(128) NULL, table_create_date DATETIME NULL, table_modify_date DATETIME NULL, no_recompute BIT NULL, has_filter BIT NULL, - filter_definition NVARCHAR(MAX) NULL + filter_definition NVARCHAR(MAX) NULL, + persisted_sample_percent FLOAT NULL, + is_incremental BIT NULL ); CREATE TABLE #ComputedColumns @@ -22841,14 +22902,15 @@ OPTION (RECOMPILE);'; BEGIN RAISERROR (N'Gathering Statistics Info With Newer Syntax.',0,1) WITH NOWAIT; SET @dsql=N'USE ' + QUOTENAME(@DatabaseName) + N'; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT #Statistics ( database_id, database_name, table_name, schema_name, index_name, column_names, statistics_name, last_statistics_update, + INSERT #Statistics ( database_id, database_name, object_id, table_name, schema_name, index_name, column_names, statistics_name, last_statistics_update, days_since_last_stats_update, rows, rows_sampled, percent_sampled, histogram_steps, modification_counter, percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date, - no_recompute, has_filter, filter_definition) + no_recompute, has_filter, filter_definition, persisted_sample_percent, is_incremental) SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], - @i_DatabaseName AS database_name, - obj.name AS table_name, - sch.name AS schema_name, + @i_DatabaseName AS database_name, + obj.object_id, + obj.name AS table_name, + sch.name AS schema_name, ISNULL(i.name, ''System Or User Statistic'') AS index_name, ca.column_names AS column_names, s.name AS statistics_name, @@ -22864,14 +22926,33 @@ OPTION (RECOMPILE);'; ELSE ddsp.modification_counter END AS percent_modifications, CASE WHEN ddsp.rows < 500 THEN 500 - ELSE CAST(( ddsp.rows * .20 ) + 500 AS INT) + ELSE CAST(( ddsp.rows * .20 ) + 500 AS BIGINT) END AS modifications_before_auto_update, ISNULL(i.type_desc, ''System Or User Statistic - N/A'') AS index_type_desc, CONVERT(DATETIME, obj.create_date) AS table_create_date, CONVERT(DATETIME, obj.modify_date) AS table_modify_date, s.no_recompute, s.has_filter, - s.filter_definition + s.filter_definition, + ' + + CASE WHEN EXISTS + ( + /* We cannot trust checking version numbers, like we did above, because Azure disagrees. */ + SELECT 1 + FROM sys.all_columns AS all_cols + WHERE all_cols.[object_id] = OBJECT_ID(N'sys.dm_db_stats_properties', N'IF') AND all_cols.[name] = N'persisted_sample_percent' + ) + THEN N'ddsp.persisted_sample_percent,' + ELSE N'NULL AS persisted_sample_percent,' END + + CASE WHEN EXISTS + ( + SELECT 1 + FROM sys.all_columns AS all_cols + WHERE all_cols.[object_id] = OBJECT_ID(N'sys.stats', N'V') AND all_cols.[name] = N'is_incremental' + ) + THEN N's.is_incremental' + ELSE N'NULL AS is_incremental' END + + N' FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj ON s.object_id = obj.object_id @@ -22916,12 +22997,13 @@ OPTION (RECOMPILE);'; BEGIN RAISERROR (N'Gathering Statistics Info With Older Syntax.',0,1) WITH NOWAIT; SET @dsql=N'USE ' + QUOTENAME(@DatabaseName) + N'; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT #Statistics(database_id, database_name, table_name, schema_name, index_name, column_names, statistics_name, + INSERT #Statistics(database_id, database_name, object_id, table_name, schema_name, index_name, column_names, statistics_name, last_statistics_update, days_since_last_stats_update, rows, modification_counter, percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date, - no_recompute, has_filter, filter_definition) + no_recompute, has_filter, filter_definition, persisted_sample_percent, is_incremental) SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], @i_DatabaseName AS database_name, + obj.object_id, obj.name AS table_name, sch.name AS schema_name, ISNULL(i.name, ''System Or User Statistic'') AS index_name, @@ -22935,7 +23017,7 @@ OPTION (RECOMPILE);'; ELSE si.rowmodctr END AS percent_modifications, CASE WHEN si.rowcnt < 500 THEN 500 - ELSE CAST(( si.rowcnt * .20 ) + 500 AS INT) + ELSE CAST(( si.rowcnt * .20 ) + 500 AS BIGINT) END AS modifications_before_auto_update, ISNULL(i.type_desc, ''System Or User Statistic - N/A'') AS index_type_desc, CONVERT(DATETIME, obj.create_date) AS table_create_date, @@ -22944,9 +23026,20 @@ OPTION (RECOMPILE);'; ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N's.has_filter, - s.filter_definition' + s.filter_definition,' ELSE N'NULL AS has_filter, - NULL AS filter_definition' END + NULL AS filter_definition,' END + /* Certainly NULL. This branch does not even join on the table that this column comes from. */ + + N'NULL AS persisted_sample_percent, + ' + + CASE WHEN EXISTS + ( + SELECT 1 + FROM sys.all_columns AS all_cols + WHERE all_cols.[object_id] = OBJECT_ID(N'sys.stats', N'V') AND all_cols.[name] = N'is_incremental' + ) + THEN N's.is_incremental' + ELSE N'NULL AS is_incremental' END + N' FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.sysindexes si @@ -25019,7 +25112,7 @@ BEGIN END; ---------------------------------------- - --Statistics Info: Check_id 90-99 + --Statistics Info: Check_id 90-99, as well as 125 ---------------------------------------- RAISERROR(N'check_id 90: Outdated statistics', 0,1) WITH NOWAIT; @@ -25068,6 +25161,43 @@ BEGIN OR (s.rows > 1000000 AND s.percent_sampled < 1) OPTION ( RECOMPILE ); + RAISERROR(N'check_id 125: Persisted Sampling Rates (Unexpected)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 125 AS check_id, + 90 AS Priority, + 'Statistics Warnings' AS findings_group, + 'Persisted Sampling Rates (Unexpected)', + s.database_name, + 'https://www.youtube.com/watch?v=V5illj_KOJg&t=758s' AS URL, + 'The persisted statistics sample rate is ' + CONVERT(NVARCHAR(100), s.persisted_sample_percent) + '%' + + CASE WHEN @UsualStatisticsSamplingPercent IS NOT NULL + THEN (N' rather than your expected @UsualStatisticsSamplingPercent value of ' + CONVERT(NVARCHAR(100), @UsualStatisticsSamplingPercent) + '%') + ELSE '' + END + + N'. This may indicate that somebody is doing statistics rocket surgery. If not, consider updating statistics more frequently.' AS details, + QUOTENAME(database_name) + '.' + QUOTENAME(s.schema_name) + '.' + QUOTENAME(s.table_name) + '.' + QUOTENAME(s.index_name) + '.' + QUOTENAME(s.statistics_name) + '.' + QUOTENAME(s.column_names) AS index_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #Statistics AS s + /* + We have to do float comparison here, so it is time to explain why @UsualStatisticsSamplingPercent is a float. + The foremost reason is that it is a float because we are comparing it to the persisted_sample_percent column in sys.dm_db_stats_properties and that column is a float. + You may correctly object that CREATE STATISTICS with a decimal as your WITH SAMPLE [...] PERCENT is a syntax error and conclude that integers are enough. + However, `WITH SAMPLE [...] ROWS` is allowed with PERSIST_SAMPLE_PERCENT = ON and you can use that to persist a non-integer sample rate. + So, yes, we really have to use floats. + */ + WHERE + /* persisted_sample_percent is either zero or NULL when the statistic is not persisted. */ + s.persisted_sample_percent > 0.0001 + AND + ( + ABS(@UsualStatisticsSamplingPercent - s.persisted_sample_percent) > 0.1 + OR @UsualStatisticsSamplingPercent IS NULL + ) + OPTION ( RECOMPILE ); + RAISERROR(N'check_id 92: Statistics with NO RECOMPUTE', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) @@ -25086,7 +25216,6 @@ BEGIN WHERE s.no_recompute = 1 OPTION ( RECOMPILE ); - RAISERROR(N'check_id 94: Check Constraints That Reference Functions', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) @@ -25158,9 +25287,9 @@ BEGIN JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE index_id NOT IN ( 0, 1 ) AND i.is_unique = 0 - /*Skipping tables created in the last week, or modified in past 2 days*/ - AND i.create_date >= DATEADD(dd,-7,GETDATE()) - AND i.modify_date > DATEADD(dd,-2,GETDATE()) + /*Skipping tables created in the last week, or modified in past 2 days*/ + AND i.create_date < DATEADD(dd,-7,GETDATE()) + AND i.modify_date < DATEADD(dd,-2,GETDATE()) OPTION ( RECOMPILE ); IF @percent_NC_indexes_unused >= 5 INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -25191,9 +25320,9 @@ BEGIN WHERE index_id NOT IN ( 0, 1 ) AND i.is_unique = 0 AND total_reads = 0 - /*Skipping tables created in the last week, or modified in past 2 days*/ - AND i.create_date >= DATEADD(dd,-7,GETDATE()) - AND i.modify_date > DATEADD(dd,-2,GETDATE()) + /*Skipping tables created in the last week, or modified in past 2 days*/ + AND i.create_date < DATEADD(dd,-7,GETDATE()) + AND i.modify_date < DATEADD(dd,-2,GETDATE()) GROUP BY i.database_name OPTION ( RECOMPILE ); @@ -25444,9 +25573,9 @@ BEGIN AND i.index_id NOT IN (0,1) /*NCs only*/ AND i.is_unique = 0 AND sz.total_reserved_MB >= CASE WHEN (@GetAllDatabases = 1 OR @Mode = 0) THEN @ThresholdMB ELSE sz.total_reserved_MB END - /*Skipping tables created in the last week, or modified in past 2 days*/ - AND i.create_date >= DATEADD(dd,-7,GETDATE()) - AND i.modify_date > DATEADD(dd,-2,GETDATE()) + /*Skipping tables created in the last week, or modified in past 2 days*/ + AND i.create_date < DATEADD(dd,-7,GETDATE()) + AND i.modify_date < DATEADD(dd,-2,GETDATE()) ORDER BY i.db_schema_object_indexid OPTION ( RECOMPILE ); @@ -26159,6 +26288,70 @@ BEGIN OPTION ( RECOMPILE ); + /* See check_id 125. */ + RAISERROR(N'check_id 126: Persisted Sampling Rates (Expected)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 126 AS check_id, + 200 AS Priority, + 'Statistics Warnings' AS findings_group, + 'Persisted Sampling Rates (Expected)', + s.database_name, + 'https://www.youtube.com/watch?v=V5illj_KOJg&t=758s' AS URL, + CONVERT(NVARCHAR(100), COUNT(*)) + ' statistic(s) with a persisted sample rate matching your desired persisted sample rate, ' + CONVERT(NVARCHAR(100), @UsualStatisticsSamplingPercent) + N'%. Set @UsualStatisticsSamplingPercent to NULL if you want to see all of them in this result set. Its default value is 100.' AS details, + s.database_name + N' (Entire database)' AS index_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #Statistics AS s + WHERE ABS(@UsualStatisticsSamplingPercent - s.persisted_sample_percent) <= 0.1 + AND @UsualStatisticsSamplingPercent IS NOT NULL + GROUP BY s.database_name + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 127: Partitioned Table Without Incremental Statistics', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary, more_info ) + SELECT 127 AS check_id, + 200 AS Priority, + 'Statistics Warnings' AS findings_group, + 'Partitioned Table Without Incremental Statistics', + partitioned_tables.database_name, + 'https://sqlperformance.com/2015/05/sql-statistics/improving-maintenance-incremental-statistics' AS URL, + 'The table ' + QUOTENAME(partitioned_tables.schema_name) + '.' + QUOTENAME(partitioned_tables.object_name) + ' is partitioned, but ' + + CONVERT(NVARCHAR(100), incremental_stats_counts.not_incremental_stats_count) + ' of its ' + CONVERT(NVARCHAR(100), incremental_stats_counts.stats_count) + + ' statistics are not incremental. If this is a sliding/rolling window table, then consider making the statistics incremental. If not, then investigate why this table is partitioned.' AS details, + partitioned_tables.object_name + N' (Entire table)' AS index_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary, + partitioned_tables.more_info + FROM + ( + SELECT s.database_id, + s.object_id, + COUNT(CASE WHEN s.is_incremental = 0 THEN 1 END) AS not_incremental_stats_count, + COUNT(*) AS stats_count + FROM #Statistics AS s + GROUP BY s.database_id, s.object_id + HAVING COUNT(CASE WHEN s.is_incremental = 0 THEN 1 END) > 0 + ) AS incremental_stats_counts + JOIN + ( + /* Just get the tables. We do not need the indexes. */ + SELECT DISTINCT i.database_name, + i.database_id, + i.object_id, + i.schema_name, + i.object_name, + /* This is a little bit dishonest, since it tells us nothing about if the statistics are incremental. */ + i.more_info + FROM #IndexSanity AS i + WHERE i.partition_key_column_name IS NOT NULL + ) AS partitioned_tables + ON partitioned_tables.database_id = incremental_stats_counts.database_id AND partitioned_tables.object_id = incremental_stats_counts.object_id + /* No need for a GROUP BY. What we are joining on has exactly one row in each sub-query. */ + OPTION ( RECOMPILE ); END /* IF @Mode = 4 */ @@ -27404,7 +27597,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.25', @VersionDate = '20250704'; + SELECT @Version = '8.26', @VersionDate = '20251002'; IF @VersionCheckMode = 1 BEGIN @@ -27951,19 +28144,17 @@ BEGIN @StringToExecute = N'SELECT @r = o.name FROM ' + @OutputDatabaseName + - N'.sys.objects AS o WHERE o.type_desc = N''USER_TABLE'' AND o.name = ' + + N'.sys.objects AS o inner join ' + + @OutputDatabaseName + + N'.sys.schemas as s on o.schema_id = s.schema_id WHERE o.type_desc = N''USER_TABLE'' AND o.name = ' + QUOTENAME ( @OutputTableName, N'''' ) + - N' AND o.schema_id = SCHEMA_ID(' + - QUOTENAME - ( - @OutputSchemaName, - N'''' - ) + - N');', + N' AND s.name =''' + + @OutputSchemaName + + N''';', @StringToExecuteParams = N'@r sysname OUTPUT'; @@ -28205,12 +28396,12 @@ BEGIN ) BEGIN RAISERROR('Found synonym DeadlockFindings, dropping', 0, 1) WITH NOWAIT; - DROP SYNONYM DeadlockFindings; + DROP SYNONYM dbo.DeadlockFindings; END; RAISERROR('Creating synonym DeadlockFindings', 0, 1) WITH NOWAIT; SET @StringToExecute = - N'CREATE SYNONYM DeadlockFindings FOR ' + + N'CREATE SYNONYM dbo.DeadlockFindings FOR ' + @OutputDatabaseName + N'.' + @OutputSchemaName + @@ -28232,12 +28423,12 @@ BEGIN ) BEGIN RAISERROR('Found synonym DeadLockTbl, dropping', 0, 1) WITH NOWAIT; - DROP SYNONYM DeadLockTbl; + DROP SYNONYM dbo.DeadLockTbl; END; RAISERROR('Creating synonym DeadLockTbl', 0, 1) WITH NOWAIT; SET @StringToExecute = - N'CREATE SYNONYM DeadLockTbl FOR ' + + N'CREATE SYNONYM dbo.DeadLockTbl FOR ' + @OutputDatabaseName + N'.' + @OutputSchemaName + @@ -31467,7 +31658,7 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - DROP SYNONYM DeadLockTbl; + DROP SYNONYM dbo.DeadLockTbl; SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Findings to table %s', 0, 1, @d) WITH NOWAIT; @@ -31497,7 +31688,7 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - DROP SYNONYM DeadlockFindings; /*done with inserting.*/ + DROP SYNONYM dbo.DeadlockFindings; /*done with inserting.*/ END; ELSE /*Output to database is not set output to client app*/ BEGIN @@ -31937,7 +32128,7 @@ ALTER PROCEDURE dbo.sp_BlitzWho @CheckDateOverride DATETIMEOFFSET = NULL, @ShowActualParameters BIT = 0, @GetOuterCommand BIT = 0, - @GetLiveQueryPlan BIT = 0, + @GetLiveQueryPlan BIT = NULL, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0, @@ -31948,7 +32139,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.25', @VersionDate = '20250704'; + SELECT @Version = '8.26', @VersionDate = '20251002'; IF(@VersionCheckMode = 1) BEGIN @@ -32000,7 +32191,8 @@ RETURN; END; /* @Help = 1 */ /* Get the major and minor build numbers */ -DECLARE @ProductVersion NVARCHAR(128) +DECLARE @ProductVersion NVARCHAR(128) = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) + ,@EngineEdition INT = CAST(SERVERPROPERTY('EngineEdition') AS INT) ,@ProductVersionMajor DECIMAL(10,2) ,@ProductVersionMinor DECIMAL(10,2) ,@Platform NVARCHAR(8) /* Azure or NonAzure are acceptable */ = (SELECT CASE WHEN @@VERSION LIKE '%Azure%' THEN N'Azure' ELSE N'NonAzure' END AS [Platform]) @@ -32037,7 +32229,6 @@ DECLARE @ProductVersion NVARCHAR(128) /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); -SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1,CHARINDEX('.', @ProductVersion) + 1 ), @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2) @@ -32048,6 +32239,14 @@ SELECT @OutputTableName = QUOTENAME(@OutputTableName), @LineFeed = CHAR(13) + CHAR(10); +IF @GetLiveQueryPlan IS NULL + BEGIN + IF @ProductVersionMajor >= 16 OR @EngineEdition NOT IN (1, 2, 3, 4) + SET @GetLiveQueryPlan = 1; + ELSE + SET @GetLiveQueryPlan = 0; + END + IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL AND EXISTS ( SELECT * FROM sys.databases @@ -32835,7 +33034,7 @@ IF @ProductVersionMajor >= 11 END+N' derp.query_plan , CAST(COALESCE(qs_live.Query_Plan, ' + CASE WHEN @GetLiveQueryPlan=1 - THEN '''''' + THEN '''''' ELSE '''''' END +') AS XML @@ -33361,7 +33560,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.25', @VersionDate = '20250704'; +SELECT @Version = '8.26', @VersionDate = '20251002'; IF(@VersionCheckMode = 1) BEGIN @@ -35024,14 +35223,14 @@ ALTER PROCEDURE [dbo].[sp_ineachdb] @VersionDate datetime = NULL OUTPUT, @VersionCheckMode bit = 0, @is_ag_writeable_copy bit = 0, - @is_query_store_on bit = 0 + @is_query_store_on bit = NULL -- WITH EXECUTE AS OWNER – maybe not a great idea, depending on the security of your system AS BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.25', @VersionDate = '20250704'; + SELECT @Version = '8.26', @VersionDate = '20251002'; IF(@VersionCheckMode = 1) BEGIN @@ -35410,10 +35609,15 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES - /*2022*/ + /*2025*/ + (17, 925, 'RC1', 'https://info.microsoft.com/ww-landing-sql-server-2025.html', '2025-09-17', '2025-12-31', '2025-12-31', 'SQL Server 2025', 'Preview RC1'), + (17, 900, 'RC0', 'https://info.microsoft.com/ww-landing-sql-server-2025.html', '2025-08-20', '2025-12-31', '2025-12-31', 'SQL Server 2025', 'Preview RC0'), (17, 800, 'CTP 2.1', 'https://info.microsoft.com/ww-landing-sql-server-2025.html', '2025-06-16', '2025-12-31', '2025-12-31', 'SQL Server 2025', 'Preview CTP 2.1'), (17, 700, 'CTP 2.0', 'https://info.microsoft.com/ww-landing-sql-server-2025.html', '2025-05-19', '2025-12-31', '2025-12-31', 'SQL Server 2025', 'Preview CTP 2.0'), /*2022*/ + (16, 4215, 'CU21', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate21', '2025-09-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 21'), + (16, 4205, 'CU20', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate20', '2025-07-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 20'), + (16, 4200, 'CU19 GDR', 'https://support.microsoft.com/en-us/help/5058721', '2025-07-08', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 19 GDR'), (16, 4195, 'CU19', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate19', '2025-05-19', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 19'), (16, 4185, 'CU18', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate18', '2025-03-13', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 18'), (16, 4175, 'CU17', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate17', '2025-01-16', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 17'), @@ -35439,8 +35643,11 @@ VALUES (16, 1050, 'RTM GDR', 'https://support.microsoft.com/kb/5021522', '2023-02-14', '2028-01-11', '2033-01-11', 'SQL Server 2022 GDR', 'RTM'), (16, 1000, 'RTM', '', '2022-11-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'RTM'), /*2019*/ - (15, 4420, 'CU32', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2019/cumulativeupdate32', '2025-02-27', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 32'), - (15, 4430, 'CU31', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2019/cumulativeupdate31', '2025-02-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 31'), + (15, 4445, 'CU32 GDR', 'https://support.microsoft.com/kb/5065222', '2025-09-09', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 32 GDR'), + (15, 4440, 'CU32 GDR', 'https://support.microsoft.com/kb/5063757', '2025-08-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 32 GDR'), + (15, 4435, 'CU32 GDR', 'https://support.microsoft.com/kb/5058722', '2025-07-08', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 32 GDR'), + (15, 4430, 'CU32', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2019/cumulativeupdate32', '2025-02-27', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 32'), + (15, 4420, 'CU31', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2019/cumulativeupdate31', '2025-02-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 31'), (15, 4415, 'CU30', 'https://support.microsoft.com/kb/5049235', '2024-12-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 30'), (15, 4405, 'CU29', 'https://support.microsoft.com/kb/5046365', '2024-10-31', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 29'), (15, 4395, 'CU28 GDR', 'https://support.microsoft.com/kb/5046060', '2024-10-08', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 28 GDR'), @@ -35479,6 +35686,9 @@ VALUES (15, 2070, 'GDR', 'https://support.microsoft.com/en-us/help/4517790', '2019-11-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'RTM GDR '), (15, 2000, 'RTM ', '', '2019-11-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'RTM '), /*2017*/ + (14, 3505, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5065225', '2025-09-09', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), + (14, 3500, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5063759', '2025-08-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), + (14, 3495, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5058714', '2025-07-08', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3485, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5046858', '2024-11-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3480, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5046061', '2024-10-08', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3475, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5042215', '2024-09-10', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), @@ -35520,6 +35730,7 @@ VALUES (14, 3006, 'RTM CU1', 'https://support.microsoft.com/en-us/help/4038634', '2017-10-24', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 1'), (14, 1000, 'RTM ', '', '2017-10-02', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM '), /*2016*/ + (13, 7055, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5058717', '2025-07-08', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'), (13, 7045, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5046062', '2024-10-08', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'), (13, 7040, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5042209', '2024-09-10', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'), (13, 7037, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5040944', '2024-07-09', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'), @@ -35527,6 +35738,9 @@ VALUES (13, 7024, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5021128', '2023-02-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'), (13, 7016, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5015371', '2022-06-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'), (13, 7000, 'SP3 Azure Feature Pack', 'https://support.microsoft.com/en-us/help/5014242', '2022-05-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack'), + (13, 6470, 'SP3 GDR', 'https://support.microsoft.com/kb/5065226', '2025-09-09', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), + (13, 6465, 'SP3 GDR', 'https://support.microsoft.com/kb/5063762', '2025-08-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), + (13, 6460, 'SP3 GDR', 'https://support.microsoft.com/kb/5058718', '2025-07-08', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6455, 'SP3 GDR', 'https://support.microsoft.com/kb/5046855', '2024-11-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6450, 'SP3 GDR', 'https://support.microsoft.com/kb/5046063', '2024-10-08', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6445, 'SP3 GDR', 'https://support.microsoft.com/kb/5042207', '2024-09-10', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), @@ -35898,7 +36112,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.25', @VersionDate = '20250704'; +SELECT @Version = '8.26', @VersionDate = '20251002'; IF(@VersionCheckMode = 1) BEGIN @@ -37489,7 +37703,11 @@ BEGIN 'Maintenance Tasks Running' AS FindingGroup, 'Restore Running' AS Finding, 'https://www.brentozar.com/askbrent/backups/' AS URL, - 'Restore of ' + COALESCE(DB_NAME(db.resource_database_id), 'Unknown Database') + ' database (' + COALESCE((SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id), 'Unknown') + 'GB) is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' AS Details, + 'Restore of ' + COALESCE(DB_NAME(db.resource_database_id), + (SELECT db1.name FROM sys.databases db1 + LEFT OUTER JOIN sys.databases db2 ON db1.name <> db2.name AND db1.state_desc = db2.state_desc + WHERE db1.state_desc = 'RESTORING' AND db2.name IS NULL), + 'Unknown Database') + ' database (' + COALESCE((SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id), 'Unknown ') + 'GB) is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' AS Details, 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, pl.query_plan AS QueryPlan, r.start_time AS StartTime, @@ -38439,6 +38657,27 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END + /* Server Performance - Azure Operation Ongoing - CheckID 53 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 53',10,1) WITH NOWAIT; + END + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_operation_status') + BEGIN + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT 53 AS CheckID, + 50 AS Priority, + 'Server Performance' AS FindingGroup, + 'Azure Operation ' + CASE WHEN state IN (2, 3, 5) THEN 'Ended Recently' ELSE 'Ongoing' END AS Finding, + 'https://learn.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-operation-status-azure-sql-database' AS URL, + N'Operation: ' + operation + N' State: ' + state_desc + N' Percent Complete: ' + CAST(percent_complete AS NVARCHAR(10)) + @LineFeed + + N' On: ' + CAST(resource_type_desc AS NVARCHAR(100)) + N':' + CAST(major_resource_id AS NVARCHAR(100)) + @LineFeed + + N' Started: ' + CAST(start_time AS NVARCHAR(100)) + N' Last Modified Time: ' + CAST(last_modify_time AS NVARCHAR(100)) + @LineFeed + + N' For more information, query SELECT * FROM sys.dm_operation_status; ' AS Details + FROM sys.dm_operation_status + END + + /* Potential Upcoming Problems - High Number of Connections - CheckID 49 */ IF (@Debug = 1) BEGIN @@ -38468,6 +38707,27 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END END + + + /* Server Performance - Memory Dangerously Low Recently - CheckID 52 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 52',10,1) WITH NOWAIT; + END + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_os_memory_health_history') + BEGIN + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT TOP 1 52 AS CheckID, + 10 AS Priority, + 'Server Performance' AS FindingGroup, + 'Memory Dangerously Low Recently' AS Finding, + 'https://www.brentozar.com/go/memhist' AS URL, + N'As recently as ' + CONVERT(NVARCHAR(19), snapshot_time, 120) + N', memory health issues are being reported in sys.dm_os_memory_health_history, indicating extreme memory pressure.' AS Details + FROM sys.dm_os_memory_health_history + WHERE severity_level > 1; + END + + RAISERROR('Finished running investigatory queries',10,1) WITH NOWAIT; diff --git a/Install-Azure.sql b/Install-Azure.sql index fbbe2acf..5f0a15cd 100644 --- a/Install-Azure.sql +++ b/Install-Azure.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.25', @VersionDate = '20250704'; +SELECT @Version = '8.26', @VersionDate = '20251002'; IF(@VersionCheckMode = 1) BEGIN @@ -1174,7 +1174,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.25', @VersionDate = '20250704'; +SELECT @Version = '8.26', @VersionDate = '20251002'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -4963,12 +4963,12 @@ SELECT @@SPID AS SPID, AND ci.comma_paren_charindex > 0 THEN SUBSTRING(ci.expression, ci.paren_charindex, ci.comma_paren_charindex) END AS converted_to, - CASE WHEN ci.at_charindex = 0 + LEFT(CASE WHEN ci.at_charindex = 0 AND ci.convert_implicit_charindex = 0 AND ci.proc_name = 'Statement' THEN SUBSTRING(ci.expression, ci.equal_charindex, 4000) ELSE '**idk_man**' - END AS compile_time_value + END, 258) AS compile_time_value FROM #conversion_info AS ci OPTION (RECOMPILE); @@ -8558,7 +8558,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.25', @VersionDate = '20250704'; +SELECT @Version = '8.26', @VersionDate = '20251002'; IF(@VersionCheckMode = 1) BEGIN @@ -10149,7 +10149,11 @@ BEGIN 'Maintenance Tasks Running' AS FindingGroup, 'Restore Running' AS Finding, 'https://www.brentozar.com/askbrent/backups/' AS URL, - 'Restore of ' + COALESCE(DB_NAME(db.resource_database_id), 'Unknown Database') + ' database (' + COALESCE((SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id), 'Unknown') + 'GB) is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '. ' AS Details, + 'Restore of ' + COALESCE(DB_NAME(db.resource_database_id), + (SELECT db1.name FROM sys.databases db1 + LEFT OUTER JOIN sys.databases db2 ON db1.name <> db2.name AND db1.state_desc = db2.state_desc + WHERE db1.state_desc = 'RESTORING' AND db2.name IS NULL), + 'Unknown Database') + ' database (' + COALESCE((SELECT CAST(CAST(SUM(size * 8.0 / 1024 / 1024) AS BIGINT) AS NVARCHAR) FROM #MasterFiles WHERE database_id = db.resource_database_id), 'Unknown ') + 'GB) is ' + CAST(r.percent_complete AS NVARCHAR(100)) + '% complete, has been running since ' + CAST(r.start_time AS NVARCHAR(100)) + '.' AS Details, 'KILL ' + CAST(r.session_id AS NVARCHAR(100)) + ';' AS HowToStopIt, pl.query_plan AS QueryPlan, r.start_time AS StartTime, @@ -11099,6 +11103,27 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END + /* Server Performance - Azure Operation Ongoing - CheckID 53 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 53',10,1) WITH NOWAIT; + END + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_operation_status') + BEGIN + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT 53 AS CheckID, + 50 AS Priority, + 'Server Performance' AS FindingGroup, + 'Azure Operation ' + CASE WHEN state IN (2, 3, 5) THEN 'Ended Recently' ELSE 'Ongoing' END AS Finding, + 'https://learn.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-operation-status-azure-sql-database' AS URL, + N'Operation: ' + operation + N' State: ' + state_desc + N' Percent Complete: ' + CAST(percent_complete AS NVARCHAR(10)) + @LineFeed + + N' On: ' + CAST(resource_type_desc AS NVARCHAR(100)) + N':' + CAST(major_resource_id AS NVARCHAR(100)) + @LineFeed + + N' Started: ' + CAST(start_time AS NVARCHAR(100)) + N' Last Modified Time: ' + CAST(last_modify_time AS NVARCHAR(100)) + @LineFeed + + N' For more information, query SELECT * FROM sys.dm_operation_status; ' AS Details + FROM sys.dm_operation_status + END + + /* Potential Upcoming Problems - High Number of Connections - CheckID 49 */ IF (@Debug = 1) BEGIN @@ -11128,6 +11153,27 @@ If one of them is a lead blocker, consider killing that query.'' AS HowToStopit, END END + + + /* Server Performance - Memory Dangerously Low Recently - CheckID 52 */ + IF (@Debug = 1) + BEGIN + RAISERROR('Running CheckID 52',10,1) WITH NOWAIT; + END + IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_os_memory_health_history') + BEGIN + INSERT INTO #BlitzFirstResults (CheckID, Priority, FindingsGroup, Finding, URL, Details) + SELECT TOP 1 52 AS CheckID, + 10 AS Priority, + 'Server Performance' AS FindingGroup, + 'Memory Dangerously Low Recently' AS Finding, + 'https://www.brentozar.com/go/memhist' AS URL, + N'As recently as ' + CONVERT(NVARCHAR(19), snapshot_time, 120) + N', memory health issues are being reported in sys.dm_os_memory_health_history, indicating extreme memory pressure.' AS Details + FROM sys.dm_os_memory_health_history + WHERE severity_level > 1; + END + + RAISERROR('Finished running investigatory queries',10,1) WITH NOWAIT; @@ -13612,6 +13658,7 @@ ALTER PROCEDURE dbo.sp_BlitzIndex /*Note:@Filter doesn't do anything unless @Mode=0*/ @SkipPartitions BIT = 0, @SkipStatistics BIT = 1, + @UsualStatisticsSamplingPercent FLOAT = 100, /* FLOAT to match sys.dm_db_stats_properties. More detail later. 100 by default because Brent suggests that if people are persisting statistics at all, they are probably doing 100 in lots of places and not filtering that out would produce noise. */ @GetAllDatabases BIT = 0, @ShowColumnstoreOnly BIT = 0, /* Will show only the Row Group and Segment details for a table with a columnstore index. */ @BringThePain BIT = 0, @@ -13638,7 +13685,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.25', @VersionDate = '20250704'; +SELECT @Version = '8.26', @VersionDate = '20251002'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) @@ -13763,6 +13810,12 @@ BEGIN RETURN; END; +IF(@UsualStatisticsSamplingPercent <= 0 OR @UsualStatisticsSamplingPercent > 100) +BEGIN + RAISERROR('Invalid value for parameter @UsualStatisticsSamplingPercent. Expected: 1 to 100',12,1); + RETURN; +END; + IF(@OutputType = 'TABLE' AND NOT (@OutputTableName IS NULL AND @OutputSchemaName IS NULL AND @OutputDatabaseName IS NULL AND @OutputServerName IS NULL)) BEGIN RAISERROR(N'One or more output parameters specified in combination with TABLE output, changing to NONE output mode', 0,1) WITH NOWAIT; @@ -14308,6 +14361,7 @@ IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL CREATE TABLE #Statistics ( database_id INT NOT NULL, database_name NVARCHAR(256) NOT NULL, + object_id INT NOT NULL, table_name NVARCHAR(128) NULL, schema_name NVARCHAR(128) NULL, index_name NVARCHAR(128) NULL, @@ -14321,13 +14375,15 @@ IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL histogram_steps INT NULL, modification_counter BIGINT NULL, percent_modifications DECIMAL(18, 1) NULL, - modifications_before_auto_update INT NULL, + modifications_before_auto_update BIGINT NULL, index_type_desc NVARCHAR(128) NULL, table_create_date DATETIME NULL, table_modify_date DATETIME NULL, no_recompute BIT NULL, has_filter BIT NULL, - filter_definition NVARCHAR(MAX) NULL + filter_definition NVARCHAR(MAX) NULL, + persisted_sample_percent FLOAT NULL, + is_incremental BIT NULL ); CREATE TABLE #ComputedColumns @@ -15865,14 +15921,15 @@ OPTION (RECOMPILE);'; BEGIN RAISERROR (N'Gathering Statistics Info With Newer Syntax.',0,1) WITH NOWAIT; SET @dsql=N'USE ' + QUOTENAME(@DatabaseName) + N'; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT #Statistics ( database_id, database_name, table_name, schema_name, index_name, column_names, statistics_name, last_statistics_update, + INSERT #Statistics ( database_id, database_name, object_id, table_name, schema_name, index_name, column_names, statistics_name, last_statistics_update, days_since_last_stats_update, rows, rows_sampled, percent_sampled, histogram_steps, modification_counter, percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date, - no_recompute, has_filter, filter_definition) + no_recompute, has_filter, filter_definition, persisted_sample_percent, is_incremental) SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], - @i_DatabaseName AS database_name, - obj.name AS table_name, - sch.name AS schema_name, + @i_DatabaseName AS database_name, + obj.object_id, + obj.name AS table_name, + sch.name AS schema_name, ISNULL(i.name, ''System Or User Statistic'') AS index_name, ca.column_names AS column_names, s.name AS statistics_name, @@ -15888,14 +15945,33 @@ OPTION (RECOMPILE);'; ELSE ddsp.modification_counter END AS percent_modifications, CASE WHEN ddsp.rows < 500 THEN 500 - ELSE CAST(( ddsp.rows * .20 ) + 500 AS INT) + ELSE CAST(( ddsp.rows * .20 ) + 500 AS BIGINT) END AS modifications_before_auto_update, ISNULL(i.type_desc, ''System Or User Statistic - N/A'') AS index_type_desc, CONVERT(DATETIME, obj.create_date) AS table_create_date, CONVERT(DATETIME, obj.modify_date) AS table_modify_date, s.no_recompute, s.has_filter, - s.filter_definition + s.filter_definition, + ' + + CASE WHEN EXISTS + ( + /* We cannot trust checking version numbers, like we did above, because Azure disagrees. */ + SELECT 1 + FROM sys.all_columns AS all_cols + WHERE all_cols.[object_id] = OBJECT_ID(N'sys.dm_db_stats_properties', N'IF') AND all_cols.[name] = N'persisted_sample_percent' + ) + THEN N'ddsp.persisted_sample_percent,' + ELSE N'NULL AS persisted_sample_percent,' END + + CASE WHEN EXISTS + ( + SELECT 1 + FROM sys.all_columns AS all_cols + WHERE all_cols.[object_id] = OBJECT_ID(N'sys.stats', N'V') AND all_cols.[name] = N'is_incremental' + ) + THEN N's.is_incremental' + ELSE N'NULL AS is_incremental' END + + N' FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj ON s.object_id = obj.object_id @@ -15940,12 +16016,13 @@ OPTION (RECOMPILE);'; BEGIN RAISERROR (N'Gathering Statistics Info With Older Syntax.',0,1) WITH NOWAIT; SET @dsql=N'USE ' + QUOTENAME(@DatabaseName) + N'; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - INSERT #Statistics(database_id, database_name, table_name, schema_name, index_name, column_names, statistics_name, + INSERT #Statistics(database_id, database_name, object_id, table_name, schema_name, index_name, column_names, statistics_name, last_statistics_update, days_since_last_stats_update, rows, modification_counter, percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date, - no_recompute, has_filter, filter_definition) + no_recompute, has_filter, filter_definition, persisted_sample_percent, is_incremental) SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id], @i_DatabaseName AS database_name, + obj.object_id, obj.name AS table_name, sch.name AS schema_name, ISNULL(i.name, ''System Or User Statistic'') AS index_name, @@ -15959,7 +16036,7 @@ OPTION (RECOMPILE);'; ELSE si.rowmodctr END AS percent_modifications, CASE WHEN si.rowcnt < 500 THEN 500 - ELSE CAST(( si.rowcnt * .20 ) + 500 AS INT) + ELSE CAST(( si.rowcnt * .20 ) + 500 AS BIGINT) END AS modifications_before_auto_update, ISNULL(i.type_desc, ''System Or User Statistic - N/A'') AS index_type_desc, CONVERT(DATETIME, obj.create_date) AS table_create_date, @@ -15968,9 +16045,20 @@ OPTION (RECOMPILE);'; ' + CASE WHEN @SQLServerProductVersion NOT LIKE '9%' THEN N's.has_filter, - s.filter_definition' + s.filter_definition,' ELSE N'NULL AS has_filter, - NULL AS filter_definition' END + NULL AS filter_definition,' END + /* Certainly NULL. This branch does not even join on the table that this column comes from. */ + + N'NULL AS persisted_sample_percent, + ' + + CASE WHEN EXISTS + ( + SELECT 1 + FROM sys.all_columns AS all_cols + WHERE all_cols.[object_id] = OBJECT_ID(N'sys.stats', N'V') AND all_cols.[name] = N'is_incremental' + ) + THEN N's.is_incremental' + ELSE N'NULL AS is_incremental' END + N' FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.sysindexes si @@ -18043,7 +18131,7 @@ BEGIN END; ---------------------------------------- - --Statistics Info: Check_id 90-99 + --Statistics Info: Check_id 90-99, as well as 125 ---------------------------------------- RAISERROR(N'check_id 90: Outdated statistics', 0,1) WITH NOWAIT; @@ -18092,6 +18180,43 @@ BEGIN OR (s.rows > 1000000 AND s.percent_sampled < 1) OPTION ( RECOMPILE ); + RAISERROR(N'check_id 125: Persisted Sampling Rates (Unexpected)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 125 AS check_id, + 90 AS Priority, + 'Statistics Warnings' AS findings_group, + 'Persisted Sampling Rates (Unexpected)', + s.database_name, + 'https://www.youtube.com/watch?v=V5illj_KOJg&t=758s' AS URL, + 'The persisted statistics sample rate is ' + CONVERT(NVARCHAR(100), s.persisted_sample_percent) + '%' + + CASE WHEN @UsualStatisticsSamplingPercent IS NOT NULL + THEN (N' rather than your expected @UsualStatisticsSamplingPercent value of ' + CONVERT(NVARCHAR(100), @UsualStatisticsSamplingPercent) + '%') + ELSE '' + END + + N'. This may indicate that somebody is doing statistics rocket surgery. If not, consider updating statistics more frequently.' AS details, + QUOTENAME(database_name) + '.' + QUOTENAME(s.schema_name) + '.' + QUOTENAME(s.table_name) + '.' + QUOTENAME(s.index_name) + '.' + QUOTENAME(s.statistics_name) + '.' + QUOTENAME(s.column_names) AS index_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #Statistics AS s + /* + We have to do float comparison here, so it is time to explain why @UsualStatisticsSamplingPercent is a float. + The foremost reason is that it is a float because we are comparing it to the persisted_sample_percent column in sys.dm_db_stats_properties and that column is a float. + You may correctly object that CREATE STATISTICS with a decimal as your WITH SAMPLE [...] PERCENT is a syntax error and conclude that integers are enough. + However, `WITH SAMPLE [...] ROWS` is allowed with PERSIST_SAMPLE_PERCENT = ON and you can use that to persist a non-integer sample rate. + So, yes, we really have to use floats. + */ + WHERE + /* persisted_sample_percent is either zero or NULL when the statistic is not persisted. */ + s.persisted_sample_percent > 0.0001 + AND + ( + ABS(@UsualStatisticsSamplingPercent - s.persisted_sample_percent) > 0.1 + OR @UsualStatisticsSamplingPercent IS NULL + ) + OPTION ( RECOMPILE ); + RAISERROR(N'check_id 92: Statistics with NO RECOMPUTE', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) @@ -18110,7 +18235,6 @@ BEGIN WHERE s.no_recompute = 1 OPTION ( RECOMPILE ); - RAISERROR(N'check_id 94: Check Constraints That Reference Functions', 0,1) WITH NOWAIT; INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, secret_columns, index_usage_summary, index_size_summary ) @@ -18182,9 +18306,9 @@ BEGIN JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id WHERE index_id NOT IN ( 0, 1 ) AND i.is_unique = 0 - /*Skipping tables created in the last week, or modified in past 2 days*/ - AND i.create_date >= DATEADD(dd,-7,GETDATE()) - AND i.modify_date > DATEADD(dd,-2,GETDATE()) + /*Skipping tables created in the last week, or modified in past 2 days*/ + AND i.create_date < DATEADD(dd,-7,GETDATE()) + AND i.modify_date < DATEADD(dd,-2,GETDATE()) OPTION ( RECOMPILE ); IF @percent_NC_indexes_unused >= 5 INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, @@ -18215,9 +18339,9 @@ BEGIN WHERE index_id NOT IN ( 0, 1 ) AND i.is_unique = 0 AND total_reads = 0 - /*Skipping tables created in the last week, or modified in past 2 days*/ - AND i.create_date >= DATEADD(dd,-7,GETDATE()) - AND i.modify_date > DATEADD(dd,-2,GETDATE()) + /*Skipping tables created in the last week, or modified in past 2 days*/ + AND i.create_date < DATEADD(dd,-7,GETDATE()) + AND i.modify_date < DATEADD(dd,-2,GETDATE()) GROUP BY i.database_name OPTION ( RECOMPILE ); @@ -18468,9 +18592,9 @@ BEGIN AND i.index_id NOT IN (0,1) /*NCs only*/ AND i.is_unique = 0 AND sz.total_reserved_MB >= CASE WHEN (@GetAllDatabases = 1 OR @Mode = 0) THEN @ThresholdMB ELSE sz.total_reserved_MB END - /*Skipping tables created in the last week, or modified in past 2 days*/ - AND i.create_date >= DATEADD(dd,-7,GETDATE()) - AND i.modify_date > DATEADD(dd,-2,GETDATE()) + /*Skipping tables created in the last week, or modified in past 2 days*/ + AND i.create_date < DATEADD(dd,-7,GETDATE()) + AND i.modify_date < DATEADD(dd,-2,GETDATE()) ORDER BY i.db_schema_object_indexid OPTION ( RECOMPILE ); @@ -19183,6 +19307,70 @@ BEGIN OPTION ( RECOMPILE ); + /* See check_id 125. */ + RAISERROR(N'check_id 126: Persisted Sampling Rates (Expected)', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary ) + SELECT 126 AS check_id, + 200 AS Priority, + 'Statistics Warnings' AS findings_group, + 'Persisted Sampling Rates (Expected)', + s.database_name, + 'https://www.youtube.com/watch?v=V5illj_KOJg&t=758s' AS URL, + CONVERT(NVARCHAR(100), COUNT(*)) + ' statistic(s) with a persisted sample rate matching your desired persisted sample rate, ' + CONVERT(NVARCHAR(100), @UsualStatisticsSamplingPercent) + N'%. Set @UsualStatisticsSamplingPercent to NULL if you want to see all of them in this result set. Its default value is 100.' AS details, + s.database_name + N' (Entire database)' AS index_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary + FROM #Statistics AS s + WHERE ABS(@UsualStatisticsSamplingPercent - s.persisted_sample_percent) <= 0.1 + AND @UsualStatisticsSamplingPercent IS NOT NULL + GROUP BY s.database_name + OPTION ( RECOMPILE ); + + RAISERROR(N'check_id 127: Partitioned Table Without Incremental Statistics', 0,1) WITH NOWAIT; + INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition, + secret_columns, index_usage_summary, index_size_summary, more_info ) + SELECT 127 AS check_id, + 200 AS Priority, + 'Statistics Warnings' AS findings_group, + 'Partitioned Table Without Incremental Statistics', + partitioned_tables.database_name, + 'https://sqlperformance.com/2015/05/sql-statistics/improving-maintenance-incremental-statistics' AS URL, + 'The table ' + QUOTENAME(partitioned_tables.schema_name) + '.' + QUOTENAME(partitioned_tables.object_name) + ' is partitioned, but ' + + CONVERT(NVARCHAR(100), incremental_stats_counts.not_incremental_stats_count) + ' of its ' + CONVERT(NVARCHAR(100), incremental_stats_counts.stats_count) + + ' statistics are not incremental. If this is a sliding/rolling window table, then consider making the statistics incremental. If not, then investigate why this table is partitioned.' AS details, + partitioned_tables.object_name + N' (Entire table)' AS index_definition, + 'N/A' AS secret_columns, + 'N/A' AS index_usage_summary, + 'N/A' AS index_size_summary, + partitioned_tables.more_info + FROM + ( + SELECT s.database_id, + s.object_id, + COUNT(CASE WHEN s.is_incremental = 0 THEN 1 END) AS not_incremental_stats_count, + COUNT(*) AS stats_count + FROM #Statistics AS s + GROUP BY s.database_id, s.object_id + HAVING COUNT(CASE WHEN s.is_incremental = 0 THEN 1 END) > 0 + ) AS incremental_stats_counts + JOIN + ( + /* Just get the tables. We do not need the indexes. */ + SELECT DISTINCT i.database_name, + i.database_id, + i.object_id, + i.schema_name, + i.object_name, + /* This is a little bit dishonest, since it tells us nothing about if the statistics are incremental. */ + i.more_info + FROM #IndexSanity AS i + WHERE i.partition_key_column_name IS NOT NULL + ) AS partitioned_tables + ON partitioned_tables.database_id = incremental_stats_counts.database_id AND partitioned_tables.object_id = incremental_stats_counts.object_id + /* No need for a GROUP BY. What we are joining on has exactly one row in each sub-query. */ + OPTION ( RECOMPILE ); END /* IF @Mode = 4 */ @@ -20428,7 +20616,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.25', @VersionDate = '20250704'; + SELECT @Version = '8.26', @VersionDate = '20251002'; IF @VersionCheckMode = 1 BEGIN @@ -20975,19 +21163,17 @@ BEGIN @StringToExecute = N'SELECT @r = o.name FROM ' + @OutputDatabaseName + - N'.sys.objects AS o WHERE o.type_desc = N''USER_TABLE'' AND o.name = ' + + N'.sys.objects AS o inner join ' + + @OutputDatabaseName + + N'.sys.schemas as s on o.schema_id = s.schema_id WHERE o.type_desc = N''USER_TABLE'' AND o.name = ' + QUOTENAME ( @OutputTableName, N'''' ) + - N' AND o.schema_id = SCHEMA_ID(' + - QUOTENAME - ( - @OutputSchemaName, - N'''' - ) + - N');', + N' AND s.name =''' + + @OutputSchemaName + + N''';', @StringToExecuteParams = N'@r sysname OUTPUT'; @@ -21229,12 +21415,12 @@ BEGIN ) BEGIN RAISERROR('Found synonym DeadlockFindings, dropping', 0, 1) WITH NOWAIT; - DROP SYNONYM DeadlockFindings; + DROP SYNONYM dbo.DeadlockFindings; END; RAISERROR('Creating synonym DeadlockFindings', 0, 1) WITH NOWAIT; SET @StringToExecute = - N'CREATE SYNONYM DeadlockFindings FOR ' + + N'CREATE SYNONYM dbo.DeadlockFindings FOR ' + @OutputDatabaseName + N'.' + @OutputSchemaName + @@ -21256,12 +21442,12 @@ BEGIN ) BEGIN RAISERROR('Found synonym DeadLockTbl, dropping', 0, 1) WITH NOWAIT; - DROP SYNONYM DeadLockTbl; + DROP SYNONYM dbo.DeadLockTbl; END; RAISERROR('Creating synonym DeadLockTbl', 0, 1) WITH NOWAIT; SET @StringToExecute = - N'CREATE SYNONYM DeadLockTbl FOR ' + + N'CREATE SYNONYM dbo.DeadLockTbl FOR ' + @OutputDatabaseName + N'.' + @OutputSchemaName + @@ -24491,7 +24677,7 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - DROP SYNONYM DeadLockTbl; + DROP SYNONYM dbo.DeadLockTbl; SET @d = CONVERT(varchar(40), GETDATE(), 109); RAISERROR('Findings to table %s', 0, 1, @d) WITH NOWAIT; @@ -24521,7 +24707,7 @@ BEGIN RAISERROR('Finished at %s', 0, 1, @d) WITH NOWAIT; - DROP SYNONYM DeadlockFindings; /*done with inserting.*/ + DROP SYNONYM dbo.DeadlockFindings; /*done with inserting.*/ END; ELSE /*Output to database is not set output to client app*/ BEGIN @@ -24961,7 +25147,7 @@ ALTER PROCEDURE dbo.sp_BlitzWho @CheckDateOverride DATETIMEOFFSET = NULL, @ShowActualParameters BIT = 0, @GetOuterCommand BIT = 0, - @GetLiveQueryPlan BIT = 0, + @GetLiveQueryPlan BIT = NULL, @Version VARCHAR(30) = NULL OUTPUT, @VersionDate DATETIME = NULL OUTPUT, @VersionCheckMode BIT = 0, @@ -24972,7 +25158,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.25', @VersionDate = '20250704'; + SELECT @Version = '8.26', @VersionDate = '20251002'; IF(@VersionCheckMode = 1) BEGIN @@ -25024,7 +25210,8 @@ RETURN; END; /* @Help = 1 */ /* Get the major and minor build numbers */ -DECLARE @ProductVersion NVARCHAR(128) +DECLARE @ProductVersion NVARCHAR(128) = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) + ,@EngineEdition INT = CAST(SERVERPROPERTY('EngineEdition') AS INT) ,@ProductVersionMajor DECIMAL(10,2) ,@ProductVersionMinor DECIMAL(10,2) ,@Platform NVARCHAR(8) /* Azure or NonAzure are acceptable */ = (SELECT CASE WHEN @@VERSION LIKE '%Azure%' THEN N'Azure' ELSE N'NonAzure' END AS [Platform]) @@ -25061,7 +25248,6 @@ DECLARE @ProductVersion NVARCHAR(128) /* Let's get @SortOrder set to lower case here for comparisons later */ SET @SortOrder = REPLACE(LOWER(@SortOrder), N' ', N'_'); -SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)); SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1,CHARINDEX('.', @ProductVersion) + 1 ), @ProductVersionMinor = PARSENAME(CONVERT(VARCHAR(32), @ProductVersion), 2) @@ -25072,6 +25258,14 @@ SELECT @OutputTableName = QUOTENAME(@OutputTableName), @LineFeed = CHAR(13) + CHAR(10); +IF @GetLiveQueryPlan IS NULL + BEGIN + IF @ProductVersionMajor >= 16 OR @EngineEdition NOT IN (1, 2, 3, 4) + SET @GetLiveQueryPlan = 1; + ELSE + SET @GetLiveQueryPlan = 0; + END + IF @OutputDatabaseName IS NOT NULL AND @OutputSchemaName IS NOT NULL AND @OutputTableName IS NOT NULL AND EXISTS ( SELECT * FROM sys.databases @@ -25859,7 +26053,7 @@ IF @ProductVersionMajor >= 11 END+N' derp.query_plan , CAST(COALESCE(qs_live.Query_Plan, ' + CASE WHEN @GetLiveQueryPlan=1 - THEN '''''' + THEN '''''' ELSE '''''' END +') AS XML diff --git a/SqlServerVersions.sql b/SqlServerVersions.sql index cb1bb869..3bb177a6 100644 --- a/SqlServerVersions.sql +++ b/SqlServerVersions.sql @@ -41,10 +41,13 @@ DELETE FROM dbo.SqlServerVersions; INSERT INTO dbo.SqlServerVersions (MajorVersionNumber, MinorVersionNumber, Branch, [Url], ReleaseDate, MainstreamSupportEndDate, ExtendedSupportEndDate, MajorVersionName, MinorVersionName) VALUES - /*2022*/ + /*2025*/ + (17, 925, 'RC1', 'https://info.microsoft.com/ww-landing-sql-server-2025.html', '2025-09-17', '2025-12-31', '2025-12-31', 'SQL Server 2025', 'Preview RC1'), + (17, 900, 'RC0', 'https://info.microsoft.com/ww-landing-sql-server-2025.html', '2025-08-20', '2025-12-31', '2025-12-31', 'SQL Server 2025', 'Preview RC0'), (17, 800, 'CTP 2.1', 'https://info.microsoft.com/ww-landing-sql-server-2025.html', '2025-06-16', '2025-12-31', '2025-12-31', 'SQL Server 2025', 'Preview CTP 2.1'), (17, 700, 'CTP 2.0', 'https://info.microsoft.com/ww-landing-sql-server-2025.html', '2025-05-19', '2025-12-31', '2025-12-31', 'SQL Server 2025', 'Preview CTP 2.0'), /*2022*/ + (16, 4215, 'CU21', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate21', '2025-09-11', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 21'), (16, 4205, 'CU20', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate20', '2025-07-10', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 20'), (16, 4200, 'CU19 GDR', 'https://support.microsoft.com/en-us/help/5058721', '2025-07-08', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 19 GDR'), (16, 4195, 'CU19', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate19', '2025-05-19', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'Cumulative Update 19'), @@ -72,6 +75,8 @@ VALUES (16, 1050, 'RTM GDR', 'https://support.microsoft.com/kb/5021522', '2023-02-14', '2028-01-11', '2033-01-11', 'SQL Server 2022 GDR', 'RTM'), (16, 1000, 'RTM', '', '2022-11-15', '2028-01-11', '2033-01-11', 'SQL Server 2022', 'RTM'), /*2019*/ + (15, 4445, 'CU32 GDR', 'https://support.microsoft.com/kb/5065222', '2025-09-09', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 32 GDR'), + (15, 4440, 'CU32 GDR', 'https://support.microsoft.com/kb/5063757', '2025-08-12', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 32 GDR'), (15, 4435, 'CU32 GDR', 'https://support.microsoft.com/kb/5058722', '2025-07-08', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 32 GDR'), (15, 4430, 'CU32', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2019/cumulativeupdate32', '2025-02-27', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 32'), (15, 4420, 'CU31', 'https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2019/cumulativeupdate31', '2025-02-13', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'Cumulative Update 31'), @@ -113,6 +118,8 @@ VALUES (15, 2070, 'GDR', 'https://support.microsoft.com/en-us/help/4517790', '2019-11-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'RTM GDR '), (15, 2000, 'RTM ', '', '2019-11-04', '2025-01-07', '2030-01-08', 'SQL Server 2019', 'RTM '), /*2017*/ + (14, 3505, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5065225', '2025-09-09', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), + (14, 3500, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5063759', '2025-08-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3495, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5058714', '2025-07-08', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3485, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5046858', '2024-11-12', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), (14, 3480, 'RTM CU31 GDR', 'https://support.microsoft.com/kb/5046061', '2024-10-08', '2022-10-11', '2027-10-12', 'SQL Server 2017', 'RTM Cumulative Update 31 GDR'), @@ -163,6 +170,9 @@ VALUES (13, 7024, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5021128', '2023-02-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'), (13, 7016, 'SP3 Azure Feature Pack GDR', 'https://support.microsoft.com/en-us/help/5015371', '2022-06-14', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack GDR'), (13, 7000, 'SP3 Azure Feature Pack', 'https://support.microsoft.com/en-us/help/5014242', '2022-05-19', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 Azure Feature Pack'), + (13, 6470, 'SP3 GDR', 'https://support.microsoft.com/kb/5065226', '2025-09-09', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), + (13, 6465, 'SP3 GDR', 'https://support.microsoft.com/kb/5063762', '2025-08-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), + (13, 6460, 'SP3 GDR', 'https://support.microsoft.com/kb/5058718', '2025-07-08', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6455, 'SP3 GDR', 'https://support.microsoft.com/kb/5046855', '2024-11-12', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6450, 'SP3 GDR', 'https://support.microsoft.com/kb/5046063', '2024-10-08', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), (13, 6445, 'SP3 GDR', 'https://support.microsoft.com/kb/5042207', '2024-09-10', '2021-07-13', '2026-07-14', 'SQL Server 2016', 'Service Pack 3 GDR'), diff --git a/sp_Blitz.sql b/sp_Blitz.sql index b0d36012..6bd44a70 100644 --- a/sp_Blitz.sql +++ b/sp_Blitz.sql @@ -39,7 +39,7 @@ AS SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.25', @VersionDate = '20250704'; + SELECT @Version = '8.26', @VersionDate = '20251002'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzAnalysis.sql b/sp_BlitzAnalysis.sql index b30dedd2..09f11f49 100644 --- a/sp_BlitzAnalysis.sql +++ b/sp_BlitzAnalysis.sql @@ -37,7 +37,7 @@ AS SET NOCOUNT ON; SET STATISTICS XML OFF; -SELECT @Version = '8.25', @VersionDate = '20250704'; +SELECT @Version = '8.26', @VersionDate = '20251002'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzBackups.sql b/sp_BlitzBackups.sql index 822d79f5..dafec335 100755 --- a/sp_BlitzBackups.sql +++ b/sp_BlitzBackups.sql @@ -24,7 +24,7 @@ AS SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.25', @VersionDate = '20250704'; + SELECT @Version = '8.26', @VersionDate = '20251002'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzCache.sql b/sp_BlitzCache.sql index 7d4175fd..0df4e8a4 100644 --- a/sp_BlitzCache.sql +++ b/sp_BlitzCache.sql @@ -283,7 +283,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.25', @VersionDate = '20250704'; +SELECT @Version = '8.26', @VersionDate = '20251002'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzFirst.sql b/sp_BlitzFirst.sql index 91245229..f5dda196 100644 --- a/sp_BlitzFirst.sql +++ b/sp_BlitzFirst.sql @@ -47,7 +47,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.25', @VersionDate = '20250704'; +SELECT @Version = '8.26', @VersionDate = '20251002'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_BlitzIndex.sql b/sp_BlitzIndex.sql index 7821b1d2..57e2760c 100644 --- a/sp_BlitzIndex.sql +++ b/sp_BlitzIndex.sql @@ -50,7 +50,7 @@ SET NOCOUNT ON; SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -SELECT @Version = '8.25', @VersionDate = '20250704'; +SELECT @Version = '8.26', @VersionDate = '20251002'; SET @OutputType = UPPER(@OutputType); IF(@VersionCheckMode = 1) diff --git a/sp_BlitzLock.sql b/sp_BlitzLock.sql index bd280b17..bd4e75da 100644 --- a/sp_BlitzLock.sql +++ b/sp_BlitzLock.sql @@ -42,7 +42,7 @@ BEGIN SET XACT_ABORT OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.25', @VersionDate = '20250704'; + SELECT @Version = '8.26', @VersionDate = '20251002'; IF @VersionCheckMode = 1 BEGIN diff --git a/sp_BlitzWho.sql b/sp_BlitzWho.sql index 0267ff3c..a9b61f89 100644 --- a/sp_BlitzWho.sql +++ b/sp_BlitzWho.sql @@ -33,7 +33,7 @@ BEGIN SET STATISTICS XML OFF; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; - SELECT @Version = '8.25', @VersionDate = '20250704'; + SELECT @Version = '8.26', @VersionDate = '20251002'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_DatabaseRestore.sql b/sp_DatabaseRestore.sql index f4b5cb85..1a087860 100755 --- a/sp_DatabaseRestore.sql +++ b/sp_DatabaseRestore.sql @@ -58,7 +58,7 @@ SET STATISTICS XML OFF; /*Versioning details*/ -SELECT @Version = '8.25', @VersionDate = '20250704'; +SELECT @Version = '8.26', @VersionDate = '20251002'; IF(@VersionCheckMode = 1) BEGIN diff --git a/sp_ineachdb.sql b/sp_ineachdb.sql index 5f81ee42..eca2b7a9 100644 --- a/sp_ineachdb.sql +++ b/sp_ineachdb.sql @@ -37,7 +37,7 @@ BEGIN SET NOCOUNT ON; SET STATISTICS XML OFF; - SELECT @Version = '8.25', @VersionDate = '20250704'; + SELECT @Version = '8.26', @VersionDate = '20251002'; IF(@VersionCheckMode = 1) BEGIN