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